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

existing_prices() click to toggle source

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
new_prices() click to toggle source

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
price_string_from_result(data, symbol: nil) click to toggle source

@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
prices_for_symbol(symbol, start_date: nil, end_date: nil) click to toggle source

@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
run!() click to toggle source

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

commodities() click to toggle source
# 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() click to toggle source

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
possible_quote_strings(commodity: nil) click to toggle source

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() click to toggle source

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