diff options
Diffstat (limited to 'src/commodity.cc')
-rw-r--r-- | src/commodity.cc | 517 |
1 files changed, 123 insertions, 394 deletions
diff --git a/src/commodity.cc b/src/commodity.cc index 5fd54d11..a72d85c8 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2010, John Wiegley. All rights reserved. + * Copyright (c) 2003-2012, John Wiegley. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -35,430 +35,162 @@ #include "commodity.h" #include "annotate.h" #include "pool.h" +#include "scope.h" namespace ledger { bool commodity_t::decimal_comma_by_default = false; -void commodity_t::history_t::add_price(commodity_t& source, - const datetime_t& date, - const amount_t& price, - const bool reflexive) +void commodity_t::add_price(const datetime_t& date, const amount_t& price, + const bool reflexive) { - DEBUG("commodity.prices.add", "add_price to " << source - << (reflexive ? " (secondary)" : " (primary)") - << " : " << date << ", " << price); - - history_map::iterator i = prices.find(date); - if (i != prices.end()) { - (*i).second = price; - } else { - std::pair<history_map::iterator, bool> result - = prices.insert(history_map::value_type(date, price)); - assert(result.second); - } - if (reflexive) { - amount_t inverse = price.inverted(); - inverse.set_commodity(const_cast<commodity_t&>(source)); - price.commodity().add_price(date, inverse, false); + DEBUG("history.find", "Marking " + << price.commodity().symbol() << " as a primary commodity"); + price.commodity().add_flags(COMMODITY_PRIMARY); } else { - DEBUG("commodity.prices.add", - "marking commodity " << source.symbol() << " as primary"); - source.add_flags(COMMODITY_PRIMARY); + DEBUG("history.find", "Marking " << symbol() << " as a primary commodity"); + add_flags(COMMODITY_PRIMARY); } -} -bool commodity_t::history_t::remove_price(const datetime_t& date) -{ - DEBUG("commodity.prices.add", "remove_price: " << date); + DEBUG("history.find", "Adding price: " << symbol() + << " for " << price << " on " << date); - history_map::size_type n = prices.erase(date); - if (n > 0) - return true; - return false; + pool().commodity_price_history.add_price(referent(), date, price); + + base->price_map.clear(); // a price was added, invalid the map } -void commodity_t::varied_history_t:: - add_price(commodity_t& source, - const datetime_t& date, - const amount_t& price, - const bool reflexive) +void commodity_t::remove_price(const datetime_t& date, commodity_t& commodity) { - optional<history_t&> hist = history(price.commodity()); - if (! hist) { - std::pair<history_by_commodity_map::iterator, bool> result - = histories.insert(history_by_commodity_map::value_type - (&price.commodity(), history_t())); - assert(result.second); - - hist = (*result.first).second; - } - assert(hist); + pool().commodity_price_history.remove_price(referent(), commodity, date); - hist->add_price(source, date, price, reflexive); + DEBUG("history.find", "Removing price: " << symbol() << " on " << date); + + base->price_map.clear(); // a price was added, invalid the map } -bool commodity_t::varied_history_t::remove_price(const datetime_t& date, - commodity_t& comm) +void commodity_t::map_prices(function<void(datetime_t, const amount_t&)> fn, + const datetime_t& moment, + const datetime_t& _oldest, + bool bidirectionally) { - DEBUG("commodity.prices.add", "varied_remove_price: " << date << ", " << comm); + datetime_t when; + if (! moment.is_not_a_date_time()) + when = moment; + else if (epoch) + when = *epoch; + else + when = CURRENT_TIME(); - if (optional<history_t&> hist = history(comm)) - return hist->remove_price(date); - return false; + pool().commodity_price_history.map_prices(fn, referent(), when, _oldest, + bidirectionally); } optional<price_point_t> -commodity_t::history_t::find_price(const optional<datetime_t>& moment, - const optional<datetime_t>& oldest -#if defined(DEBUG_ON) - , const int indent -#endif - ) const +commodity_t::find_price_from_expr(expr_t& expr, const commodity_t * commodity, + const datetime_t& moment) const { - price_point_t point; - bool found = false; - -#if defined(DEBUG_ON) -#define DEBUG_INDENT(cat, indent) \ - do { \ - if (SHOW_DEBUG(cat)) \ - for (int _i = 0; _i < indent; _i++) \ - ledger::_log_buffer << " "; \ - } while (false) -#else -#define DEBUG_INDENT(cat, indent) -#endif - #if defined(DEBUG_ON) - DEBUG_INDENT("commodity.prices.find", indent); - if (moment) - DEBUG("commodity.prices.find", "find price nearest before or on: " << *moment); - else - DEBUG("commodity.prices.find", "find any price"); - - if (oldest) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "but no older than: " << *oldest); + if (SHOW_DEBUG("commodity.price.find")) { + ledger::_log_buffer << "valuation expr: "; + expr.dump(ledger::_log_buffer); + DEBUG("commodity.price.find", ""); } #endif + value_t result(expr.calc(*scope_t::default_scope)); - if (prices.size() == 0) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "there are no prices in this history"); - return none; - } - - if (! moment) { - history_map::const_reverse_iterator r = prices.rbegin(); - point.when = (*r).first; - point.price = (*r).second; - found = true; + if (is_expr(result)) { + value_t call_args; - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "using most recent price"); - } else { - history_map::const_iterator i = prices.upper_bound(*moment); - if (i == prices.end()) { - history_map::const_reverse_iterator r = prices.rbegin(); - point.when = (*r).first; - point.price = (*r).second; - found = true; - - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "using last price"); - } else { - point.when = (*i).first; - if (*moment < point.when) { - if (i != prices.begin()) { - --i; - point.when = (*i).first; - point.price = (*i).second; - found = true; - } - } else { - point.price = (*i).second; - found = true; - } + call_args.push_back(string_value(base_symbol())); + call_args.push_back(moment); + if (commodity) + call_args.push_back(string_value(commodity->symbol())); - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "using found price"); - } + result = as_expr(result)->call(call_args, *scope_t::default_scope); } - if (! found) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "could not find a price"); - return none; - } - else if (moment && point.when > *moment) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "price is too young "); - return none; - } - else if (oldest && point.when < *oldest) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "price is too old "); - return none; - } - else { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", - "returning price: " << point.when << ", " << point.price); - return point; - } + return price_point_t(moment, result.to_amount()); } optional<price_point_t> -commodity_t::varied_history_t::find_price(const commodity_t& source, - const optional<commodity_t&>& commodity, - const optional<datetime_t>& moment, - const optional<datetime_t>& oldest -#if defined(DEBUG_ON) - , const int indent -#endif - ) const +commodity_t::find_price(const commodity_t * commodity, + const datetime_t& moment, + const datetime_t& oldest) const { - optional<price_point_t> point; - optional<datetime_t> limit = oldest; - -#if defined(VERIFY_ON) - if (commodity) { - VERIFY(source != *commodity); - VERIFY(! commodity->has_annotation()); - VERIFY(source.referent() != commodity->referent()); - } -#endif - -#if defined(DEBUG_ON) - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "varied_find_price for: " << source); + DEBUG("commodity.price.find", "commodity_t::find_price(" << symbol() << ")"); - DEBUG_INDENT("commodity.prices.find", indent); + const commodity_t * target = NULL; if (commodity) - DEBUG("commodity.prices.find", "looking for: commodity '" << *commodity << "'"); - else - DEBUG("commodity.prices.find", "looking for: any commodity"); - - if (moment) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "time index: " << *moment); - } - - if (oldest) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "only consider prices younger than: " << *oldest); - } -#endif - - // Either we couldn't find a history for the target commodity, or we - // couldn't find a price. In either case, search all histories known - // to this commodity for a price which we can calculate in terms of - // the goal commodity. - price_point_t best; - bool found = false; - - foreach (const history_by_commodity_map::value_type& hist, histories) { - commodity_t& comm(*hist.first); - if (comm == source) - continue; - - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", - "searching for price via commodity '" << comm << "'"); - - point = hist.second.find_price(moment, limit -#if defined(DEBUG_ON) - , indent + 2 -#endif - ); - assert(! point || point->price.commodity() == comm); - - if (point) { - optional<price_point_t> xlat; + target = commodity; + else if (pool().default_commodity) + target = &*pool().default_commodity; - if (commodity && comm != *commodity) { - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", "looking for translation price"); - - xlat = comm.find_price(commodity, moment, limit, true -#if defined(DEBUG_ON) - , indent + 2 -#endif - ); - if (xlat) { - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", "found translated price " - << xlat->price << " from " << xlat->when); - - point->price = xlat->price * point->price; - if (xlat->when < point->when) { - point->when = xlat->when; - - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", - "adjusting date of result back to " << point->when); - } - } else { - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", "saw no translated price there"); - continue; - } - } - - assert(! commodity || point->price.commodity() == *commodity); - - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", - "saw a price there: " << point->price << " from " << point->when); + if (target && this == target) + return none; - if (! limit || point->when > *limit) { - limit = point->when; - best = *point; - found = true; + base_t::memoized_price_entry entry(moment, oldest, + commodity ? commodity : NULL); - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", - "search limit adjusted to " << *limit); - } - } else { - DEBUG_INDENT("commodity.prices.find", indent + 1); - DEBUG("commodity.prices.find", "saw no price there"); + DEBUG("commodity.price.find", "looking for memoized args: " + << (! moment.is_not_a_date_time() ? format_datetime(moment) : "NONE") << ", " + << (! oldest.is_not_a_date_time() ? format_datetime(oldest) : "NONE") << ", " + << (commodity ? commodity->symbol() : "NONE")); + { + base_t::memoized_price_map::iterator i = base->price_map.find(entry); + if (i != base->price_map.end()) { + DEBUG("commodity.price.find", "found! returning: " + << ((*i).second ? (*i).second->price : amount_t(0L))); + return (*i).second; } } - if (found) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.download", - "found price " << best.price << " from " << best.when); - return best; - } - return none; -} - -optional<commodity_t::history_t&> -commodity_t::varied_history_t::history(const optional<commodity_t&>& commodity) -{ - commodity_t * comm = NULL; - if (! commodity) { - if (histories.size() > 1) - return none; - comm = (*histories.begin()).first; - } else { - comm = &(*commodity); + datetime_t when; + if (! moment.is_not_a_date_time()) + when = moment; + else if (epoch) + when = *epoch; + else + when = CURRENT_TIME(); + + if (base->value_expr) + return find_price_from_expr(*base->value_expr, commodity, when); + + optional<price_point_t> + point(target ? + pool().commodity_price_history.find_price(referent(), *target, + when, oldest) : + pool().commodity_price_history.find_price(referent(), when, oldest)); + + // Record this price point in the memoization map + if (base->price_map.size() > base_t::max_price_map_size) { + DEBUG("history.find", + "price map has grown too large, clearing it by half"); + for (std::size_t i = 0; i < base_t::max_price_map_size >> 1; i++) + base->price_map.erase(base->price_map.begin()); } - history_by_commodity_map::iterator i = histories.find(comm); - if (i != histories.end()) - return (*i).second; - - return none; -} - -optional<price_point_t> -commodity_t::find_price(const optional<commodity_t&>& commodity, - const optional<datetime_t>& moment, - const optional<datetime_t>& oldest, - const bool nested -#if defined(DEBUG_ON) - , const int indent -#endif - ) const -{ - if (! has_flags(COMMODITY_WALKED) && base->varied_history) { - optional<base_t::time_and_commodity_t> pair; -#if defined(VERIFY_ON) - optional<price_point_t> checkpoint; - bool found = false; -#endif - - if (! nested) { - pair = base_t::time_and_commodity_t - (base_t::optional_time_pair_t(moment, oldest), - commodity ? &(*commodity) : NULL); - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "looking for memoized args: " - << (moment ? format_datetime(*moment) : "NONE") << ", " - << (oldest ? format_datetime(*oldest) : "NONE") << ", " - << (commodity ? commodity->symbol() : "NONE")); - - base_t::memoized_price_map::iterator i = base->price_map.find(*pair); - if (i != base->price_map.end()) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "found! returning: " - << ((*i).second ? (*i).second->price : amount_t(0L))); -#if defined(VERIFY_ON) - IF_VERIFY() { - found = true; - checkpoint = (*i).second; - } else -#endif // defined(VERIFY_ON) - return (*i).second; - } - } - - optional<price_point_t> point; - - const_cast<commodity_t&>(*this).add_flags(COMMODITY_WALKED); - try { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", "manually finding price..."); + DEBUG("history.find", + "remembered: " << (point ? point->price : amount_t(0L))); + base->price_map.insert(base_t::memoized_price_map::value_type(entry, point)); - point = base->varied_history->find_price(*this, commodity, - moment, oldest -#if defined(DEBUG_ON) - , indent -#endif - ); - } - catch (...) { - const_cast<commodity_t&>(*this).drop_flags(COMMODITY_WALKED); - throw; - } - const_cast<commodity_t&>(*this).drop_flags(COMMODITY_WALKED); - -#if defined(VERIFY_ON) - if (DO_VERIFY() && found) { - VERIFY(checkpoint == point); - return checkpoint; - } -#endif // defined(VERIFY_ON) - - if (! nested && pair) { - if (base->price_map.size() > base_t::max_price_map_size) { - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", - "price map has grown too large, clearing it by half"); - - for (std::size_t i = 0; i < base_t::max_price_map_size >> 1; i++) - base->price_map.erase(base->price_map.begin()); - } - - DEBUG_INDENT("commodity.prices.find", indent); - DEBUG("commodity.prices.find", - "remembered: " << (point ? point->price : amount_t(0L))); - base->price_map.insert - (base_t::memoized_price_map::value_type(*pair, point)); - } - return point; - } - return none; + return point; } optional<price_point_t> commodity_t::check_for_updated_price(const optional<price_point_t>& point, - const optional<datetime_t>& moment, - const optional<commodity_t&>& in_terms_of) + const datetime_t& moment, + const commodity_t* in_terms_of) { if (pool().get_quotes && ! has_flags(COMMODITY_NOMARKET)) { bool exceeds_leeway = true; if (point) { time_duration_t::sec_type seconds_diff; - if (moment) { - seconds_diff = (*moment - point->when).total_seconds(); - DEBUG("commodity.download", "moment = " << *moment); + if (! moment.is_not_a_date_time()) { + seconds_diff = (moment - point->when).total_seconds(); + DEBUG("commodity.download", "moment = " << moment); DEBUG("commodity.download", "slip.moment = " << seconds_diff); } else { seconds_diff = (TRUE_CURRENT_TIME() - point->when).total_seconds(); @@ -474,10 +206,10 @@ commodity_t::check_for_updated_price(const optional<price_point_t>& point, DEBUG("commodity.download", "attempting to download a more current quote..."); if (optional<price_point_t> quote = - pool().get_commodity_quote(*this, in_terms_of)) { + pool().get_commodity_quote(referent(), in_terms_of)) { if (! in_terms_of || (quote->price.has_commodity() && - quote->price.commodity() == *in_terms_of)) + quote->price.commodity_ptr() == in_terms_of)) return quote; } } @@ -485,6 +217,16 @@ commodity_t::check_for_updated_price(const optional<price_point_t>& point, return point; } +commodity_t& commodity_t::nail_down(const expr_t& expr) +{ + annotation_t new_details; + + new_details.value_expr = expr; + new_details.add_flags(ANNOTATION_VALUE_EXPR_CALCULATED); + + return *pool().find_or_create(symbol(), new_details); +} + commodity_t::operator bool() const { return this != pool().null_commodity; @@ -641,7 +383,7 @@ void commodity_t::parse_symbol(char *& p, string& symbol) throw_(amount_error, _("Failed to parse commodity")); } -void commodity_t::print(std::ostream& out, bool elide_quotes) const +void commodity_t::print(std::ostream& out, bool elide_quotes, bool) const { string sym = symbol(); if (elide_quotes && has_flags(COMMODITY_STYLE_SEPARATED) && @@ -740,6 +482,15 @@ bool commodity_t::compare_by_commodity::operator()(const amount_t * left, if (aleftcomm.details.tag && arightcomm.details.tag) return *aleftcomm.details.tag < *arightcomm.details.tag; + if (! aleftcomm.details.value_expr && arightcomm.details.value_expr) + return true; + if (aleftcomm.details.value_expr && ! arightcomm.details.value_expr) + return false; + + if (aleftcomm.details.value_expr && arightcomm.details.value_expr) + return (aleftcomm.details.value_expr->text() < + arightcomm.details.value_expr->text()); + assert(false); return true; } @@ -767,28 +518,6 @@ void to_xml(std::ostream& out, const commodity_t& comm, if (commodity_details) { if (comm.has_annotation()) to_xml(out, as_annotated_commodity(comm).details); - - if (comm.varied_history()) { - push_xml y(out, "varied-history"); - - foreach (const commodity_t::history_by_commodity_map::value_type& pair, - comm.varied_history()->histories) { - { - push_xml z(out, "symbol"); - out << y.guard(pair.first->symbol()); - } - { - push_xml z(out, "history"); - - foreach (const commodity_t::history_map::value_type& inner_pair, - pair.second.prices) { - push_xml w(out, "price-point"); - to_xml(out, inner_pair.first); - to_xml(out, inner_pair.second); - } - } - } - } } } |