Automatically adding the @ price to postings

76 views
Skip to first unread message

jitin

unread,
May 11, 2020, 3:29:29 PM5/11/20
to Beancount
Hi,

I have postings which are something like:

2017-04-03 * "someone"
Income:Sales:International -42.5 USD
Equity:Drawings 2524.68 INR
Expenses:Banking:PaypalCharges

However Benacount reports them as 4 transactions in a non-intuitive way:

Income:Sales:International    -42.5 USD -42.5 USD
Expenses:Banking:PaypalCharges 42.5 USD 42.5 USD
Equity:Drawings 2524.68 INR 2524.68 INR
Expenses:Banking:PaypalCharges -2524.68 INR -2524.68 INR

If however I add a price information to the posting like this:

2017-04-03 * "someone"
Income:Sales:International -42.5 USD @65 INR
Equity:Drawings 2524.68 INR
Expenses:Banking:PaypalCharges

Beancount reports it as 3 transactions in the way I want:

Income:Sales:International    -42.5 USD 65 INR -2762.5 INR
Equity:Drawings                2524.68 INR 2524.68 INR
Expenses:Banking:PaypalCharges 237.82 INR 237.82 INR


I have already added price entries like this:
2014-01-01 commodity USD

2015-08-30 price USD 66.15 INR
2015-08-31 price USD 66.15 INR
2015-09-01 price USD 66.35 INR
...

Is there any way for Beancount to pick up the price (the @65 INR part) automatically from this price list according to the transaction date, without me manually specifying the USD to INR price for each transaction?

Thanks a lot.

Martin Blais

unread,
May 11, 2020, 9:42:15 PM5/11/20
to Beancount
Not at the moment. Two reasons for this:
- Balancing Beancount transactions can be mildly tricky, so limiting the effects to be local only to the data input to the transaction has been driving the design.
Non-local effects of changes to the price database could be tricky to debug, e.g., you insert a transaction with a price attached to a posting, that inserts a price point in the database, and then a bunch of unrelated transactions break because the prices of their conversions is changed.
(You could argue that reducing lots is a function of the inventory of the account prior to applying the transaction and that this is not local, sure, but notice that it's a bit tricky enough to resolve those at times.)
- Generally speaking the rate at which a price conversion is made is an explicit specific number that is subject to being input in your transaction. I don't think it's common that one would want to use some arbitrary rate (but I'm not 100% sure). In your example, the actual PayPal charge is a precise number. Unless all you want is an approximation (I doubt it), you still have to input it.
That being said, I think in a future version that could be considered, it's been requested frequently.

 

Thanks a lot.

--
You received this message because you are subscribed to the Google Groups "Beancount" group.
To unsubscribe from this group and stop receiving emails from it, send an email to beancount+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/beancount/def09727-5aa1-4490-b8ed-428bde58b5f9%40googlegroups.com.

jitin

unread,
May 12, 2020, 3:51:42 PM5/12/20
to Beancount
Hi,

Thanks a lot for replying.

- As a workaround to the issue you mention, maybe a single file can be specified to populate a new price database for automatically applying these prices. That way if a user inserts a transaction with a particular price attached to a posting, the database won't pick it up as it is only using the one file to get the price points. But I understand your broader point.

- For my use case, what's happening is that Paypal charges me more money in the form of currency conversion by offering me an unfavourable rate (~5% lower than the market rate), than they charge in their "fees" (which is a percentage of sales). My country's central bank provides a reference conversion rate that I can compare against. The difference between how much I would have got with the reference rate for that day versus how much Paypal actually paid me gives me the actual fee that Paypal is charging me. The reference rate is also used for other stuff in a similar way, as being the official exchange rate, so it is useful to store it in a database and have Beancount refer to it automatically.

I decided to make a plugin for this. It seems to works perfectly. I was able to apply the price to the relevant postings and also adjust the automatic postings appropriately, but with this done, I needed Beancount to process the file and apply all the transformations, etc. However I could not find a way to call a plugin before the transformations, etc. are done, so I ended up using code from beancount.loader to process the entries again once I had applied the price. This works just fine, but for future use is there a more direct/appropriate way of doing this? To get the plugin to work after Beancount has parsed the files and made entries but before it actually starts working on the entries?

This is the plugin:

#autofetch_prices.py
#input: currencyList as "targetCurrency/baseCurrency", for example: plugin "autofetch_prices" "USD/CAD"
#finds all postings in targetCurrency where the price is not set, fetches the appropriate price for that date and sets it.
#example: for input "USD/CAD", every entry like "10 USD" gets a price and becomes something like "10 USD @ 1.22 CAD"


import beancount
from beancount.core.interpolate import AUTOMATIC_META
from beancount.core.number import MISSING
from beancount.core import data
from beancount.parser import booking
from beancount.ops import validation
from beancount.utils import misc_utils

__plugins__ = ['autofetch_prices']


def autofetch_prices(entries, options_map, currencyList):
targetCurrency, baseCurrency = currencyList.split('/')
priceMap = beancount.core.prices.build_price_map(entries)
for entry in entries:
if isinstance(entry, data.Transaction):
found = False
for k, posting in enumerate(entry.postings):
if posting.price==None and posting.units.currency==targetCurrency and posting.meta and not AUTOMATIC_META in posting.meta:
price = beancount.core.prices.get_price(priceMap, targetCurrency+"/"+baseCurrency, entry.date)[1]
#if there's no price from the priceDB for this posting, do nothing and move over to the next posting.
if price==None:
continue
found = True
priceAmount = beancount.core.amount.Amount(price, baseCurrency)
posting = posting._replace(price=priceAmount)
entry.postings[k] = posting
if found:
for z, posting in enumerate(entry.postings):
if posting.meta and AUTOMATIC_META in posting.meta and posting.meta[AUTOMATIC_META]:
if posting.units.currency==targetCurrency:
entry.postings.remove(posting)
if posting.units.currency==baseCurrency:
posting = posting._replace(units = MISSING)
del posting.meta[AUTOMATIC_META]
entry.postings[z] = posting
#remove this plugin now as it has done it's job and we don't want infinite recursion next
for plugin in options_map["plugin"]:
if plugin[0]==__plugins__[0]:
options_map["plugin"].remove(plugin)



#The following code is from the beancount.loader._load function
entries.sort(key=data.entry_sortkey)

# Run interpolation on incomplete entries.
entries, balance_errors = booking.book(entries, options_map)
parse_errors = []
parse_errors.extend(balance_errors)

# Transform the entries.
entries, errors = beancount.loader.run_transformations(entries, parse_errors, options_map, None)

# Validate the list of entries.
with misc_utils.log_time('beancount.ops.validate', None, indent=1):
valid_errors = validation.validate(entries, options_map, None)
errors.extend(valid_errors)

# Compute the input hash.
options_map['input_hash'] = beancount.loader.compute_input_hash(options_map['include'])

return entries, errors



Thanks!
Jitin
To unsubscribe from this group and stop receiving emails from it, send an email to bean...@googlegroups.com.
To unsubscribe from this group and stop receiving emails from it, send an email to bean...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages