diff options
author | John Wiegley <johnw@newartisans.com> | 2009-06-24 16:43:46 +0100 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2009-06-24 16:43:46 +0100 |
commit | 991e3a3eaf2c31f856faa85cfed514bbb28be526 (patch) | |
tree | 56bfc7e97de0b0b7855a2dea180f43f6c828d16a /src/pool.cc | |
parent | 77d69d0e240287a104223a2c32b10d70ed58ca7b (diff) | |
download | fork-ledger-991e3a3eaf2c31f856faa85cfed514bbb28be526.tar.gz fork-ledger-991e3a3eaf2c31f856faa85cfed514bbb28be526.tar.bz2 fork-ledger-991e3a3eaf2c31f856faa85cfed514bbb28be526.zip |
Split commodity.h/cc into three files
commodity.h - code for commodity_t
annotate.h - commodity annotations
pool.h - commodity pool management
Diffstat (limited to 'src/pool.cc')
-rw-r--r-- | src/pool.cc | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/src/pool.cc b/src/pool.cc new file mode 100644 index 00000000..3ced7c6f --- /dev/null +++ b/src/pool.cc @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2003-2009, 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 + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <system.hh> + +#include "amount.h" +#include "commodity.h" +#include "annotate.h" +#include "pool.h" + +namespace ledger { + +commodity_pool_t::commodity_pool_t() + : default_commodity(NULL), keep_base(false), + download_leeway(86400), download_quotes(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); +} + +optional<price_point_t> commodity_pool_t::parse_price_directive(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; + commodity_t::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; +} + + +#if 0 +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]) { + if (char * p = std::strchr(buf, '\n')) *p = '\0'; + DEBUG("commodity.download", "downloaded quote: " << buf); + + if (optional<price_point_t> point = parse_commodity_price(buf)) { + 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 %3\")") + << symbol() << symbol() << (commodity ? commodity->symbol() : "''")); + } + return none; +} +#endif + +void commodity_pool_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); +} + +cost_breakdown_t +commodity_pool_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 * +commodity_pool_t::parse_price_expression(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 |