ledger/src/pool.cc

360 lines
10 KiB
C++

/*
* 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"
#include "quotes.h"
namespace ledger {
shared_ptr<commodity_pool_t> commodity_pool_t::current_pool;
commodity_pool_t::commodity_pool_t()
: default_commodity(NULL), keep_base(false),
quote_leeway(86400), get_quotes(false),
get_commodity_quote(commodity_quote_from_script)
{
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;
}
string commodity_pool_t::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.pool().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);
}
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;
}
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 if (std::isdigit(date_field_ptr[0])) {
symbol_and_price = time_field_ptr;
datetime = datetime_t(parse_date(date_field));
}
else {
symbol_and_price = date_field_ptr;
datetime = CURRENT_TIME();
}
string symbol;
commodity_t::parse_symbol(symbol_and_price, symbol);
price_point_t point;
point.when = datetime;
point.price.parse(symbol_and_price, PARSE_NO_MIGRATE);
VERIFY(point.price.valid());
DEBUG("commodity.download", "Looking up symbol: " << symbol);
if (commodity_t * commodity = find_or_create(symbol)) {
DEBUG("commodity.download", "Adding price for " << symbol << ": "
<< point.when << " " << point.price);
commodity->add_price(point.when, point.price, true);
commodity->add_flags(COMMODITY_KNOWN);
return point;
}
return none;
}
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