diff options
Diffstat (limited to 'src/commodity.cc')
-rw-r--r-- | src/commodity.cc | 563 |
1 files changed, 20 insertions, 543 deletions
diff --git a/src/commodity.cc b/src/commodity.cc index 81c9b7e6..db7fabd6 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -33,13 +33,11 @@ #include "amount.h" #include "commodity.h" +#include "annotate.h" +#include "pool.h" namespace ledger { -optional<path> commodity_t::price_db; -long commodity_t::download_leeway = 86400; -bool commodity_t::download_quotes; - void commodity_t::base_t::history_t::add_price(commodity_t& source, const datetime_t& date, const amount_t& price, @@ -99,8 +97,9 @@ void commodity_t::base_t::varied_history_t:: hist->add_price(source, date, price, reflexive); } -bool commodity_t::base_t::varied_history_t::remove_price(const datetime_t& date, - commodity_t& comm) +bool commodity_t::base_t::varied_history_t:: + remove_price(const datetime_t& date, + commodity_t& comm) { DEBUG("commodity.prices.add", "varied_remove_price: " << date << ", " << comm); @@ -109,104 +108,6 @@ bool commodity_t::base_t::varied_history_t::remove_price(const datetime_t& date return false; } -optional<price_point_t> commodity_t::parse_commodity_price(char * line) -{ - char * date_field_ptr = line; - char * time_field_ptr = next_element(date_field_ptr); - if (! time_field_ptr) return none; - string date_field = date_field_ptr; - - char * symbol_and_price; - datetime_t datetime; - - if (std::isdigit(time_field_ptr[0])) { - symbol_and_price = next_element(time_field_ptr); - if (! symbol_and_price) return none; - datetime = parse_datetime(date_field + " " + time_field_ptr); - } else { - symbol_and_price = time_field_ptr; - datetime = parse_datetime(date_field); - } - - string symbol; - parse_symbol(symbol_and_price, symbol); - - price_point_t point; - point.when = datetime; - point.price.parse(symbol_and_price); - VERIFY(point.price.valid()); - - if (commodity_t * commodity = - amount_t::current_pool->find_or_create(symbol)) { - commodity->add_price(point.when, point.price, true); - commodity->add_flags(COMMODITY_KNOWN); - return point; - } - - return none; -} - - -optional<price_point_t> -commodity_t::download_quote(const optional<commodity_t&>& commodity) const -{ - DEBUG("commodity.download", "downloading quote for symbol " << symbol()); -#if defined(DEBUG_ON) - if (commodity) - DEBUG("commodity.download", - " in terms of commodity " << commodity->symbol()); -#endif - - char buf[256]; - buf[0] = '\0'; - - string getquote_cmd("getquote \""); - getquote_cmd += symbol(); - getquote_cmd += "\" \""; - if (commodity) - getquote_cmd += commodity->symbol(); - getquote_cmd += "\""; - - DEBUG("commodity.download", "invoking command: " << getquote_cmd); - - bool success = true; - if (FILE * fp = popen(getquote_cmd.c_str(), "r")) { - if (std::feof(fp) || ! std::fgets(buf, 255, fp)) - success = false; - if (pclose(fp) != 0) - success = false; - } else { - success = false; - } - - if (success && buf[0]) { - char * p = std::strchr(buf, '\n'); - if (p) *p = '\0'; - - DEBUG("commodity.download", "downloaded quote: " << buf); - - optional<price_point_t> point = parse_commodity_price(buf); - - if (point) { - if (price_db) { -#if defined(__GNUG__) && __GNUG__ < 3 - ofstream database(*price_db, ios::out | ios::app); -#else - ofstream database(*price_db, std::ios_base::out | std::ios_base::app); -#endif - database << "P " << format_datetime(point->when, string("%Y/%m/%d %H:%M:%S")) - << " " << symbol() << " " << point->price << std::endl; - } - return point; - } - } else { - throw_(std::runtime_error, - _("Failed to download price for '%1' (command: \"getquote %2\")") - << symbol() << symbol()); - } - return none; -} - optional<price_point_t> commodity_t::base_t::history_t:: find_price(const optional<datetime_t>& moment, @@ -445,27 +346,27 @@ optional<price_point_t> " found price " << best.price << " from " << best.when); DEBUG("commodity.download", "found price " << best.price << " from " << best.when); - if (moment) - DEBUG("commodity.download", "moment = " << *moment); - DEBUG("commodity.download", "leeway = " << download_leeway); - if (moment) - DEBUG("commodity.download", - "slip.moment = " << (*moment - best.when).total_seconds()); - else - DEBUG("commodity.download", - "slip.now = " << (CURRENT_TIME() - best.when).total_seconds()); #endif - if (download_quotes && - ! source.has_flags(COMMODITY_NOMARKET) && - ((! moment && - (CURRENT_TIME() - best.when).total_seconds() > download_leeway) || - (moment && - (*moment - best.when).total_seconds() > download_leeway))) { +#if 0 + DEBUG("commodity.download", "leeway = " << download_leeway); + datetime_t::sec_type seconds_diff; + if (moment) { + seconds_diff = (*moment - best.when).total_seconds(); + DEBUG("commodity.download", "moment = " << *moment); + DEBUG("commodity.download", "slip.moment = " << seconds_diff); + } else { + seconds_diff = (CURRENT_TIME() - best.when).total_seconds(); + DEBUG("commodity.download", "slip.now = " << seconds_diff); + } + + if (download_quotes && ! source.has_flags(COMMODITY_NOMARKET) && + seconds_diff > download_leeway) { DEBUG("commodity.download", "attempting to download a more current quote..."); if (optional<price_point_t> quote = source.download_quote(commodity)) return quote; } +#endif return best; } return none; @@ -497,82 +398,6 @@ optional<commodity_t::base_t::history_t&> return none; } -void commodity_t::exchange(commodity_t& commodity, - const amount_t& per_unit_cost, - const datetime_t& moment) -{ - DEBUG("commodity.prices.add", "exchanging commodity " << commodity - << " at per unit cost " << per_unit_cost << " on " << moment); - - commodity_t& base_commodity - (commodity.annotated ? - as_annotated_commodity(commodity).referent() : commodity); - - base_commodity.add_price(moment, per_unit_cost); -} - -commodity_t::cost_breakdown_t -commodity_t::exchange(const amount_t& amount, - const amount_t& cost, - const bool is_per_unit, - const optional<datetime_t>& moment, - const optional<string>& tag) -{ - DEBUG("commodity.prices.add", "exchange: " << amount << " for " << cost); - DEBUG("commodity.prices.add", "exchange: is-per-unit = " << is_per_unit); -#if defined(DEBUG_ON) - if (moment) - DEBUG("commodity.prices.add", "exchange: moment = " << *moment); - if (tag) - DEBUG("commodity.prices.add", "exchange: tag = " << *tag); -#endif - - commodity_t& commodity(amount.commodity()); - - annotation_t * current_annotation = NULL; - if (commodity.annotated) - current_annotation = &as_annotated_commodity(commodity).details; - - amount_t per_unit_cost = - (is_per_unit || amount.is_realzero() ? cost : cost / amount).abs(); - - DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost); - - if (! per_unit_cost.is_realzero()) - exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME()); - - cost_breakdown_t breakdown; - breakdown.final_cost = ! is_per_unit ? cost : cost * amount; - - DEBUG("commodity.prices.add", - "exchange: final-cost = " << breakdown.final_cost); - - if (current_annotation && current_annotation->price) - breakdown.basis_cost - = (*current_annotation->price * amount).unrounded(); - else - breakdown.basis_cost = breakdown.final_cost; - - DEBUG("commodity.prices.add", - "exchange: basis-cost = " << breakdown.basis_cost); - - annotation_t annotation(per_unit_cost, moment ? - moment->date() : optional<date_t>(), tag); - - annotation.add_flags(ANNOTATION_PRICE_CALCULATED); - if (moment) - annotation.add_flags(ANNOTATION_DATE_CALCULATED); - if (tag) - annotation.add_flags(ANNOTATION_TAG_CALCULATED); - - breakdown.amount = amount_t(amount, annotation); - - DEBUG("commodity.prices.add", - "exchange: amount = " << breakdown.amount); - - return breakdown; -} - commodity_t::operator bool() const { return this != parent().null_commodity; @@ -752,159 +577,6 @@ bool commodity_t::valid() const return true; } -void annotation_t::parse(std::istream& in) -{ - do { - istream_pos_type pos = in.tellg(); - - char buf[256]; - char c = peek_next_nonws(in); - if (c == '{') { - if (price) - throw_(amount_error, _("Commodity specifies more than one price")); - - in.get(c); - c = peek_next_nonws(in); - if (c == '=') { - in.get(c); - add_flags(ANNOTATION_PRICE_FIXATED); - } - - READ_INTO(in, buf, 255, c, c != '}'); - if (c == '}') - in.get(c); - else - throw_(amount_error, _("Commodity price lacks closing brace")); - - amount_t temp; - temp.parse(buf, amount_t::PARSE_NO_MIGRATE); - - DEBUG("commodity.annotations", "Parsed annotation price: " << temp); - - // Since this price will maintain its own precision, make sure - // it is at least as large as the base commodity, since the user - // may have only specified {$1} or something similar. - - if (temp.has_commodity() && - temp.precision() > temp.commodity().precision()) - temp = temp.rounded(); // no need to retain individual precision - - price = temp; - } - else if (c == '[') { - if (date) - throw_(amount_error, _("Commodity specifies more than one date")); - - in.get(c); - READ_INTO(in, buf, 255, c, c != ']'); - if (c == ']') - in.get(c); - else - throw_(amount_error, _("Commodity date lacks closing bracket")); - - date = parse_date(buf); - } - else if (c == '(') { - if (tag) - throw_(amount_error, _("Commodity specifies more than one tag")); - - in.get(c); - READ_INTO(in, buf, 255, c, c != ')'); - if (c == ')') - in.get(c); - else - throw_(amount_error, _("Commodity tag lacks closing parenthesis")); - - tag = buf; - } - else { - in.clear(); - in.seekg(pos, std::ios::beg); - break; - } - } while (true); - -#if defined(DEBUG_ON) - if (SHOW_DEBUG("amounts.commodities") && *this) { - DEBUG("amounts.commodities", - "Parsed commodity annotations: " << std::endl << *this); - } -#endif -} - -bool annotated_commodity_t::operator==(const commodity_t& comm) const -{ - // If the base commodities don't match, the game's up. - if (base != comm.base) - return false; - - assert(annotated); - if (! comm.annotated) - return false; - - if (details != as_annotated_commodity(comm).details) - return false; - - return true; -} - -commodity_t& -annotated_commodity_t::strip_annotations(const keep_details_t& what_to_keep) -{ - DEBUG("commodity.annotated.strip", - "Reducing commodity " << *this << std::endl - << " keep price " << what_to_keep.keep_price << " " - << " keep date " << what_to_keep.keep_date << " " - << " keep tag " << what_to_keep.keep_tag); - - commodity_t * new_comm; - - bool keep_price = (what_to_keep.keep_price && - (! what_to_keep.only_actuals || - ! details.has_flags(ANNOTATION_PRICE_CALCULATED))); - bool keep_date = (what_to_keep.keep_date && - (! what_to_keep.only_actuals || - ! details.has_flags(ANNOTATION_DATE_CALCULATED))); - bool keep_tag = (what_to_keep.keep_tag && - (! what_to_keep.only_actuals || - ! details.has_flags(ANNOTATION_TAG_CALCULATED))); - - if ((keep_price && details.price) || - (keep_date && details.date) || - (keep_tag && details.tag)) - { - new_comm = parent().find_or_create - (referent(), annotation_t(keep_price ? details.price : none, - keep_date ? details.date : none, - keep_tag ? details.tag : none)); - } else { - new_comm = parent().find_or_create(base_symbol()); - } - - assert(new_comm); - return *new_comm; -} - -void annotated_commodity_t::write_annotations(std::ostream& out) const -{ - details.print(out, parent().keep_base); -} - -void annotation_t::print(std::ostream& out, bool keep_base) const -{ - if (price) - out << " {" - << (has_flags(ANNOTATION_PRICE_FIXATED) ? "=" : "") - << (keep_base ? *price : price->unreduced()).rounded() - << '}'; - - if (date) - out << " [" << format_date(*date, string("%Y/%m/%d")) << ']'; - - if (tag) - out << " (" << *tag << ')'; -} - bool compare_amount_commodities::operator()(const amount_t * left, const amount_t * right) const { @@ -972,199 +644,4 @@ bool compare_amount_commodities::operator()(const amount_t * left, } } -commodity_pool_t::commodity_pool_t() - : default_commodity(NULL), keep_base(false) -{ - TRACE_CTOR(commodity_pool_t, ""); - null_commodity = create(""); - null_commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET); -} - -commodity_t * commodity_pool_t::create(const string& symbol) -{ - shared_ptr<commodity_t::base_t> - base_commodity(new commodity_t::base_t(symbol)); - std::auto_ptr<commodity_t> commodity(new commodity_t(this, base_commodity)); - - DEBUG("amounts.commodities", "Creating base commodity " << symbol); - - // Create the "qualified symbol" version of this commodity's symbol - if (commodity_t::symbol_needs_quotes(symbol)) { - commodity->qualified_symbol = "\""; - *commodity->qualified_symbol += symbol; - *commodity->qualified_symbol += "\""; - } - - DEBUG("amounts.commodities", - "Creating commodity '" << commodity->symbol() << "'"); - - std::pair<commodities_map::iterator, bool> result - = commodities.insert(commodities_map::value_type(commodity->mapping_key(), - commodity.get())); - assert(result.second); - - return commodity.release(); -} - -commodity_t * commodity_pool_t::find_or_create(const string& symbol) -{ - DEBUG("amounts.commodities", "Find-or-create commodity " << symbol); - - commodity_t * commodity = find(symbol); - if (commodity) - return commodity; - return create(symbol); -} - -commodity_t * commodity_pool_t::find(const string& symbol) -{ - DEBUG("amounts.commodities", "Find commodity " << symbol); - - commodities_map::const_iterator i = commodities.find(symbol); - if (i != commodities.end()) - return (*i).second; - return NULL; -} - -commodity_t * -commodity_pool_t::create(const string& symbol, const annotation_t& details) -{ - commodity_t * new_comm = create(symbol); - if (! new_comm) - return NULL; - - if (details) - return find_or_create(*new_comm, details); - else - return new_comm; -} - -namespace { - string make_qualified_name(const commodity_t& comm, - const annotation_t& details) - { - assert(details); - - if (details.price && details.price->sign() < 0) - throw_(amount_error, _("A commodity's price may not be negative")); - - std::ostringstream name; - comm.print(name); - details.print(name, comm.parent().keep_base); - - DEBUG("amounts.commodities", "make_qualified_name for " - << *comm.qualified_symbol << std::endl << details); - DEBUG("amounts.commodities", "qualified_name is " << name.str()); - - return name.str(); - } -} - -commodity_t * -commodity_pool_t::find(const string& symbol, const annotation_t& details) -{ - commodity_t * comm = find(symbol); - if (! comm) - return NULL; - - if (details) { - string name = make_qualified_name(*comm, details); - - if (commodity_t * ann_comm = find(name)) { - assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); - return ann_comm; - } - return NULL; - } else { - return comm; - } -} - -commodity_t * -commodity_pool_t::find_or_create(const string& symbol, - const annotation_t& details) -{ - commodity_t * comm = find(symbol); - if (! comm) - return NULL; - - if (details) - return find_or_create(*comm, details); - else - return comm; -} - -commodity_t * -commodity_pool_t::create(commodity_t& comm, - const annotation_t& details, - const string& mapping_key) -{ - assert(comm); - assert(details); - assert(! mapping_key.empty()); - - std::auto_ptr<commodity_t> commodity - (new annotated_commodity_t(&comm, details)); - - commodity->qualified_symbol = comm.symbol(); - assert(! commodity->qualified_symbol->empty()); - - DEBUG("amounts.commodities", "Creating annotated commodity " - << "symbol " << commodity->symbol() - << " key " << mapping_key << std::endl << details); - - // Add the fully annotated name to the map, so that this symbol may - // quickly be found again. - commodity->mapping_key_ = mapping_key; - - std::pair<commodities_map::iterator, bool> result - = commodities.insert(commodities_map::value_type(mapping_key, - commodity.get())); - assert(result.second); - - return commodity.release(); -} - -commodity_t * commodity_pool_t::find_or_create(commodity_t& comm, - const annotation_t& details) -{ - assert(comm); - assert(details); - - string name = make_qualified_name(comm, details); - assert(! name.empty()); - - if (commodity_t * ann_comm = find(name)) { - assert(ann_comm->annotated && as_annotated_commodity(*ann_comm).details); - return ann_comm; - } - return create(comm, details, name); -} - -commodity_t * -commodity_pool_t::parse_commodity_prices(const std::string& str, - const bool add_prices, - const optional<datetime_t>& moment) -{ - scoped_array<char> buf(new char[str.length() + 1]); - - std::strcpy(buf.get(), str.c_str()); - - char * price = std::strchr(buf.get(), '='); - if (price) - *price++ = '\0'; - - if (commodity_t * commodity = find_or_create(trim_ws(buf.get()))) { - if (price && add_prices) { - for (char * p = std::strtok(price, ";"); - p; - p = std::strtok(NULL, ";")) { - commodity->add_price(moment ? *moment : CURRENT_TIME(), amount_t(p)); - } - } - return commodity; - } - return NULL; -} - } // namespace ledger |