class LedgerGetPrices::GetPrices
Synopsis¶ ↑
Tool that uses Yahoo finance to intelligently generate a ledger price database based on your current ledger commodities and time period.
Ensure that you have set the LEDGER
and LEDGER_PRICE_DB
environment variables before proceeding. Alternatively, you can make the same addition to your .ledgerrc
, so long as running ledger
vanilla knows where to get the journal and pricedb.
Yahoo works best when fetching all prices relative to USD. So regardless of whether or not USD is actually used, it will always appear in the resulting prices file.
Options¶ ↑
LEDGER_PRICE_DATE_FORMAT
: The date format of the outputted pricedb. Defaults to +%Y/%m/%d+.
Constants
- BASE_CURRENCY
The base currency should be the currency that the dollar sign represents in your reports.
- COMMODITY_BLACKLIST
- DATE_FORMAT
- PRICE_DB_PATH
- PRICE_FORMAT
Public Class Methods
We work with the database as an array of price definitions @return [Array] an array of formatted prices
# File lib/ledger_get_prices.rb, line 45 def existing_prices @existing_prices ||= File.read(PRICE_DB_PATH) .split("\n") .reject { |x| (/^P.*$/ =~ x) != 0 } end
This method builds a new price database intelligently.
@return [Array] an array of formatted prices
# File lib/ledger_get_prices.rb, line 54 def new_prices # Since everything is relative to USD, we don't need to fetch it. commodities .reject { |c| c == "USD" } .reduce(existing_prices) do |db, c| # `|` is a shortcut for merge db | prices_for_symbol(c, start_date: start_date, end_date: end_date) .map { |x| price_string_from_result(x, symbol: c) } end end
@return [String]
# File lib/ledger_get_prices.rb, line 103 def price_string_from_result(data, symbol: nil) raise "Must pass symbol" if symbol.nil? PRICE_FORMAT % { date: Date.strptime(data.trade_date, '%Y-%m-%d').strftime(DATE_FORMAT), time: '23:59:59', symbol: 'USD', price: symbol + data.close } end
@return [Array] of YahooFinance results (OpenStruct)
# File lib/ledger_get_prices.rb, line 66 def prices_for_symbol(symbol, start_date: nil, end_date: nil) # -> Array puts "Getting historical quotes for: #{symbol}" if COMMODITY_BLACKLIST.include?(symbol) puts "Skipping #{symbol}: blacklisted." puts "Use `LEDGER_PRICE_COMMODITY_BLACKLIST` to configure the blacklist." puts "BTC is included by default because yahoo doesn't provide a way to " + "get historical data for it." return [] end result = nil quote_strings = possible_quote_strings(commodity: symbol) err = nil while quote_strings.length > 0 && result.nil? begin result = YahooFinance::Client.new.historical_quotes( quote_strings.shift, start_date: start_date, end_date: end_date, period: :daily) rescue OpenURI::HTTPError => e err = e end end if result.nil? puts "Could not get quotes from Yahoo for: #{symbol}" puts "It may be worthwhile getting prices for this manually." [] else result end end
With a bang because it does a file write.
# File lib/ledger_get_prices.rb, line 39 def run! File.write(PRICE_DB_PATH, new_prices.join("\n")) end
Protected Class Methods
# File lib/ledger_get_prices.rb, line 140 def commodities # All the commodities we care about. @commodities ||= `ledger commodities`.split("\n") .reject { |x| x == "$" } .tap { |c| c << BASE_CURRENCY } .uniq end
End date is today, wish I can see the future but unfortunately.. @return [Date]
# File lib/ledger_get_prices.rb, line 128 def end_date @end_date ||= Date.new() end
Try the commodity as a currency first, before trying it as a stock @return [Array<String>] Possible Yahoo finance compatible quote strings
# File lib/ledger_get_prices.rb, line 134 def possible_quote_strings(commodity: nil) raise "No commodity given" if commodity.nil? # We get all quotes in USD ["#{commodity}=X", "USD#{commodity}=X", "#{commodity}"] end
Start date is either the latest price record or the earliest ever transaction. @return [Date]
# File lib/ledger_get_prices.rb, line 118 def start_date @start_date ||= existing_prices.map { |x| Date.strptime(x.split(" ")[1], DATE_FORMAT) }.max || begin stats = `ledger stats` # Most compact way to retrieve this data date_str = /Time\speriod:\s*([\d\w\-]*)\s*to/.match(stats)[1] return Date.strptime(date_str, "%y-%b-%d") end end