summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/commodity.cc269
-rw-r--r--src/commodity.h94
-rw-r--r--test/unit/t_commodity.cc19
3 files changed, 271 insertions, 111 deletions
diff --git a/src/commodity.cc b/src/commodity.cc
index 2a4bdcc9..ad0d6ec9 100644
--- a/src/commodity.cc
+++ b/src/commodity.cc
@@ -44,10 +44,10 @@
namespace ledger {
void commodity_t::base_t::history_t::add_price(const datetime_t& date,
- const amount_t& price)
+ const amount_t& price,
+ const bool reflexive)
{
- DEBUG("commodity.prices",
- "add_price: " << date << ", " << price);
+ DEBUG("commodity.prices", "add_price: " << date << ", " << price);
history_map::iterator i = prices.find(date);
if (i != prices.end()) {
@@ -57,6 +57,10 @@ void commodity_t::base_t::history_t::add_price(const datetime_t& date,
= prices.insert(history_map::value_type(date, price));
assert(result.second);
}
+
+ if (reflexive) {
+ price.commodity().add_price(date, *one / price, false);
+ }
}
bool commodity_t::base_t::history_t::remove_price(const datetime_t& date)
@@ -70,10 +74,10 @@ bool commodity_t::base_t::history_t::remove_price(const datetime_t& date)
}
void commodity_t::base_t::varied_history_t::add_price(const datetime_t& date,
- const amount_t& price)
+ const amount_t& price,
+ const bool reflexive)
{
- DEBUG("commodity.prices",
- "varied_add_price: " << date << ", " << price);
+ DEBUG("commodity.prices", "varied_add_price: " << date << ", " << price);
optional<history_t&> hist = history(price.commodity());
if (! hist) {
@@ -86,71 +90,90 @@ void commodity_t::base_t::varied_history_t::add_price(const datetime_t& date,
}
assert(hist);
- hist->add_price(date, price);
+ hist->add_price(date, price, reflexive);
}
bool commodity_t::base_t::varied_history_t::remove_price(const datetime_t& date,
commodity_t& comm)
{
- DEBUG("commodity.prices",
- "varied_remove_price: " << date << ", " << comm);
+ DEBUG("commodity.prices", "varied_remove_price: " << date << ", " << comm);
if (optional<history_t&> hist = history(comm))
return hist->remove_price(date);
return false;
}
-optional<amount_t>
-commodity_t::base_t::history_t::find_price(const optional<datetime_t>& moment)
+optional<price_point_t>
+ commodity_t::base_t::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
+ )
{
- optional<datetime_t> age;
- optional<amount_t> price;
+ price_point_t point;
+ bool found = false;
+
+#define DEBUG_INDENT(cat, indent) \
+ do { \
+ if (SHOW_DEBUG(cat)) \
+ for (int i = 0; i < indent; i++) \
+ ledger::_log_buffer << " "; \
+ } while (false)
#if defined(DEBUG_ON)
- if (moment) {
- DEBUG("commodity.prices", "find_price: " << *moment);
- } else {
- DEBUG("commodity.prices", "find_price");
+ DEBUG_INDENT("commodity.prices", indent);
+ if (moment)
+ DEBUG("commodity.prices", "find price nearest before or on: " << *moment);
+ else
+ DEBUG("commodity.prices", "find any price");
+
+ if (oldest) {
+ DEBUG_INDENT("commodity.prices", indent);
+ DEBUG("commodity.prices", " but no older than: " << *oldest);
}
#endif
if (prices.size() == 0) {
- DEBUG("commodity.prices", " there are no prices in the history");
+ DEBUG_INDENT("commodity.prices", indent);
+ DEBUG("commodity.prices", " there are no prices in this history");
return none;
}
if (! moment) {
history_map::const_reverse_iterator r = prices.rbegin();
- age = (*r).first;
- price = (*r).second;
-
- DEBUG("commodity.prices",
- " returning most recent price: " << age << ", " << price);
+ point.when = (*r).first;
+ point.price = (*r).second;
+ found = true;
+ DEBUG_INDENT("commodity.prices", indent);
+ DEBUG("commodity.prices", " using most recent price");
} else {
history_map::const_iterator i = prices.lower_bound(*moment);
if (i == prices.end()) {
history_map::const_reverse_iterator r = prices.rbegin();
- age = (*r).first;
- price = (*r).second;
-
- DEBUG("commodity.prices",
- " returning last price: " << age << ", " << price);
+ point.when = (*r).first;
+ point.price = (*r).second;
+ found = true;
+ DEBUG_INDENT("commodity.prices", indent);
+ DEBUG("commodity.prices", " using last price");
} else {
- age = (*i).first;
- if (*moment != *age) {
+ point.when = (*i).first;
+ if (*moment < point.when) {
if (i != prices.begin()) {
--i;
- age = (*i).first;
- price = (*i).second;
- } else {
- age = none;
+ point.when = (*i).first;
+ point.price = (*i).second;
+ found = true;
}
} else {
- price = (*i).second;
+ point.price = (*i).second;
+ found = true;
}
-
- DEBUG("commodity.prices",
- " returning found price: " << age << ", " << price);
+ DEBUG_INDENT("commodity.prices", indent);
+ DEBUG("commodity.prices", " using found price");
}
}
@@ -164,89 +187,171 @@ commodity_t::base_t::history_t::find_price(const optional<datetime_t>& moment)
}
#endif
- return price;
+ if (! found) {
+ DEBUG_INDENT("commodity.prices", indent);
+ DEBUG("commodity.prices", " could not find a price");
+ return none;
+ }
+ else if (moment && point.when > *moment) {
+ DEBUG_INDENT("commodity.prices", indent);
+ DEBUG("commodity.prices", " price is too young ");
+ return none;
+ }
+ else if (oldest && point.when < *oldest) {
+ DEBUG_INDENT("commodity.prices", indent);
+ DEBUG("commodity.prices", " price is too old ");
+ return none;
+ }
+ else {
+ DEBUG_INDENT("commodity.prices", indent);
+ DEBUG("commodity.prices",
+ " returning price: " << point.when << ", " << point.price);
+ return point;
+ }
}
-optional<amount_t>
-commodity_t::base_t::varied_history_t::find_price
- (const optional<commodity_t&>& commodity,
- const optional<datetime_t>& moment)
+optional<price_point_t>
+ commodity_t::base_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
+ )
{
- optional<amount_t> amt;
+ optional<price_point_t> point;
+ optional<datetime_t> limit = oldest;
+
+ assert(! commodity || source != *commodity);
#if defined(DEBUG_ON)
- DEBUG("commodity.prices", "varied_find_price");
+ DEBUG_INDENT("commodity.prices", indent);
+ DEBUG("commodity.prices", "varied_find_price for: " << source);
+ DEBUG_INDENT("commodity.prices", indent);
if (commodity)
- DEBUG("commodity.prices", " looking for commodity '" << *commodity << "'");
+ DEBUG("commodity.prices", " looking for: commodity '" << *commodity << "'");
else
- DEBUG("commodity.prices", " looking for any commodity");
+ DEBUG("commodity.prices", " looking for: any commodity");
- if (moment)
+ if (moment) {
+ DEBUG_INDENT("commodity.prices", indent);
DEBUG("commodity.prices", " time index: " << *moment);
-#endif
-
- if (optional<history_t&> hist = history(commodity)) {
- DEBUG("commodity.prices", " found a history for the commodity");
-
- amt = hist->find_price(moment);
+ }
-#if defined(DEBUG_ON)
- if (amt)
- DEBUG("commodity.prices", " found price in that history: " << *amt);
- else
- DEBUG("commodity.prices", " found no price in that history");
-#endif
+ if (oldest) {
+ DEBUG_INDENT("commodity.prices", indent);
+ DEBUG("commodity.prices", " 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.
- if (! amt && commodity) {
- foreach (history_by_commodity_map::value_type hist, histories) {
- commodity_t& comm(*hist.first);
+ price_point_t best;
+ bool found = false;
- DEBUG("commodity.prices",
- " searching for price via commodity '" << comm << "'");
+ foreach (history_by_commodity_map::value_type hist, histories) {
+ commodity_t& comm(*hist.first);
+ if (comm == source)
+ continue;
- amt = comm.find_price(commodity, moment);
- // jww (2008-09-24): look for the most recent match
+ DEBUG_INDENT("commodity.prices", indent + 1);
+ DEBUG("commodity.prices",
+ " searching for price via commodity '" << comm << "'");
+
+ point = hist.second.find_price(source, commodity, moment, limit, indent + 2);
+ assert(! point || point->price.commodity() == comm);
+
+ if (point) {
+ optional<price_point_t> xlat;
+
+ if (commodity && comm != *commodity) {
+ DEBUG_INDENT("commodity.prices", indent + 1);
+ DEBUG("commodity.prices", " looking for translation price");
+
+ xlat = comm.find_price(commodity, moment, limit, indent + 2);
+ if (xlat) {
+ DEBUG_INDENT("commodity.prices", indent + 1);
+ DEBUG("commodity.prices", " 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", indent + 1);
+ DEBUG("commodity.prices",
+ " adjusting date of result back to " << point->when);
+ }
+ } else {
+ DEBUG_INDENT("commodity.prices", indent + 1);
+ DEBUG("commodity.prices", " saw no translated price there");
+ continue;
+ }
+ }
-#if defined(DEBUG_ON)
- if (amt)
- DEBUG("commodity.prices", " found price there: " << *amt);
- else
- DEBUG("commodity.prices", " found no price there");
-#endif
+ assert(! commodity || point->price.commodity() == *commodity);
+ DEBUG_INDENT("commodity.prices", indent + 1);
+ DEBUG("commodity.prices", " saw a price there: " << point->price);
+ if (! limit || point->when > *limit) {
+ limit = point->when;
+ best = *point;
+ found = true;
+ }
+ } else {
+ DEBUG_INDENT("commodity.prices", indent + 1);
+ DEBUG("commodity.prices", " saw no price there");
}
}
- return amt;
+ if (found) {
+ DEBUG_INDENT("commodity.prices", indent);
+ DEBUG("commodity.prices",
+ " found price " << best.price << " from " << best.when);
+ return best;
+ }
+ return none;
}
-optional<amount_t>
-commodity_t::base_t::varied_history_t::find_price
- (const std::vector<commodity_t *>& commodities,
- const optional<datetime_t>& moment)
+optional<price_point_t>
+ commodity_t::base_t::varied_history_t::
+ find_price(const commodity_t& source,
+ const std::vector<commodity_t *>& commodities,
+ const optional<datetime_t>& moment,
+ const optional<datetime_t>& oldest
+#if defined(DEBUG_ON)
+ , const int indent
+#endif
+ )
{
foreach (commodity_t * commodity, commodities) {
- if (optional<amount_t> amt = find_price(*commodity, moment))
- return amt;
+ if (optional<price_point_t> point = find_price(source, *commodity,
+ moment, oldest
+#if defined(DEBUG_ON)
+ , indent
+#endif
+ ))
+ return point;
}
return none;
}
optional<commodity_t::base_t::history_t&>
-commodity_t::base_t::varied_history_t::history
- (const optional<commodity_t&>& commodity)
+ commodity_t::base_t::varied_history_t::
+ history(const optional<commodity_t&>& commodity)
{
commodity_t * comm = NULL;
if (! commodity) {
if (histories.size() > 1)
+ return none;
+#if 0
// jww (2008-09-20): Document which option switch to use here
throw_(commodity_error,
"Cannot determine price history: prices known for multiple commodities (use -?)");
+#endif
comm = (*histories.begin()).first;
} else {
comm = &(*commodity);
diff --git a/src/commodity.h b/src/commodity.h
index 1277b2c2..6c9a7586 100644
--- a/src/commodity.h
+++ b/src/commodity.h
@@ -52,6 +52,12 @@ namespace ledger {
DECLARE_EXCEPTION(commodity_error, std::runtime_error);
+struct price_point_t
+{
+ datetime_t when;
+ amount_t price;
+};
+
class commodity_t
: public delegates_flags<>,
public equality_comparable1<commodity_t, noncopyable>
@@ -71,10 +77,19 @@ public:
history_map prices;
ptime last_lookup;
- void add_price(const datetime_t& date, const amount_t& price);
+ void add_price(const datetime_t& date, const amount_t& price,
+ const bool reflexive = true);
bool remove_price(const datetime_t& date);
- optional<amount_t> find_price(const optional<datetime_t>& moment = none);
+ optional<price_point_t>
+ find_price(const commodity_t& source,
+ const optional<commodity_t&>& commodity,
+ const optional<datetime_t>& moment = none,
+ const optional<datetime_t>& oldest = none
+#if defined(DEBUG_ON)
+ , const int indent = 0
+#endif
+ );
};
typedef std::map<commodity_t *, history_t> history_by_commodity_map;
@@ -83,15 +98,28 @@ public:
{
history_by_commodity_map histories;
- void add_price(const datetime_t& date, const amount_t& price);
+ void add_price(const datetime_t& date, const amount_t& price,
+ const bool reflexive = true);
bool remove_price(const datetime_t& date, commodity_t& commodity);
- optional<amount_t>
- find_price(const optional<commodity_t&>& commodity = none,
- const optional<datetime_t>& moment = none);
- optional<amount_t>
- find_price(const std::vector<commodity_t *>& commodities,
- const optional<datetime_t>& moment = none);
+ optional<price_point_t>
+ find_price(const commodity_t& source,
+ const optional<commodity_t&>& commodity = none,
+ const optional<datetime_t>& moment = none,
+ const optional<datetime_t>& oldest = none
+#if defined(DEBUG_ON)
+ , const int indent = 0
+#endif
+ );
+ optional<price_point_t>
+ find_price(const commodity_t& source,
+ const std::vector<commodity_t *>& commodities,
+ const optional<datetime_t>& moment = none,
+ const optional<datetime_t>& oldest = none
+#if defined(DEBUG_ON)
+ , const int indent = 0
+#endif
+ );
optional<history_t&>
history(const optional<commodity_t&>& commodity = none);
@@ -229,25 +257,19 @@ protected:
return none;
}
- optional<history_t&>
- history(const optional<commodity_t&>& commodity);
- optional<history_t&>
- history(const std::vector<commodity_t *>& commodities);
-
- optional<history_t>
- find_price(commodity_t& commodity,
- const optional<datetime_t>& moment,
- std::vector<bool *>& bools);
+ optional<history_t&> history(const optional<commodity_t&>& commodity);
+ optional<history_t&> history(const std::vector<commodity_t *>& commodities);
public:
// These methods provide a transparent pass-through to the underlying
// base->varied_history object.
- void add_price(const datetime_t& date, const amount_t& price) {
+ void add_price(const datetime_t& date, const amount_t& price,
+ const bool reflexive = true) {
if (! base->varied_history)
base->varied_history = varied_history_t();
- base->varied_history->add_price(date, price);
+ base->varied_history->add_price(date, price, reflexive);
}
bool remove_price(const datetime_t& date, commodity_t& commodity) {
if (base->varied_history)
@@ -255,19 +277,37 @@ public:
return false;
}
- optional<amount_t>
- find_price(const optional<commodity_t&>& commodity = none,
- const optional<datetime_t>& moment = none) {
+ optional<price_point_t>
+ find_price(const optional<commodity_t&>& commodity = none,
+ const optional<datetime_t>& moment = none,
+ const optional<datetime_t>& oldest = none
+#if defined(DEBUG_ON)
+ , const int indent = 0
+#endif
+ ) {
if (base->varied_history)
- return base->varied_history->find_price(commodity, moment);
+ return base->varied_history->find_price(*this, commodity, moment, oldest
+#if defined(DEBUG_ON)
+ , indent
+#endif
+ );
return none;
}
- optional<amount_t>
+ optional<price_point_t>
find_price(const std::vector<commodity_t *>& commodities,
- const optional<datetime_t>& moment = none) {
+ const optional<datetime_t>& moment = none,
+ const optional<datetime_t>& oldest = none
+#if defined(DEBUG_ON)
+ , const int indent = 0
+#endif
+ ) {
if (base->varied_history)
- return base->varied_history->find_price(commodities, moment);
+ return base->varied_history->find_price(*this, commodities, moment, oldest
+#if defined(DEBUG_ON)
+ , indent
+#endif
+ );
return none;
}
diff --git a/test/unit/t_commodity.cc b/test/unit/t_commodity.cc
index a96bed72..7a2a2dea 100644
--- a/test/unit/t_commodity.cc
+++ b/test/unit/t_commodity.cc
@@ -35,14 +35,29 @@ void CommodityTestCase::testPriceHistory()
aapl.add_price(feb28_07sbm, amount_t("$18.30"));
aapl.add_price(mar01_07, amount_t("$19.50"));
aapl.add_price(apr15_07, amount_t("$21.22"));
+ aapl.add_price(apr15_07, amount_t("EUR 23.00"));
optional<amount_t> amt1 = x1.value(feb28_07sbm);
assertTrue(amt1);
assertEqual(amount_t("$1831.83"), *amt1);
- optional<amount_t> amt2 = x1.value(current_time);
+ commodity_t& euro(amount_t("EUR 1.00").commodity());
+
+ optional<amount_t> amt2 = x1.value(current_time, euro);
assertTrue(amt2);
- assertEqual(amount_t("$2124.12"), *amt2);
+ assertEqual(amount_t("EUR 2302.30"), *amt2);
+
+ optional<amount_t> amt3 = x1.value(current_time);
+ assertTrue(amt3);
+ assertEqual(amount_t("$2124.12"), *amt3);
+
+ euro.add_price(feb27_07, amount_t("CAD 40.00"));
+
+ commodity_t& cad(amount_t("CAD 1.00").commodity());
+
+ optional<amount_t> amt4 = x1.value(current_time, cad);
+ assertTrue(amt4);
+ assertEqual(amount_t("CAD 92092.00"), *amt4);
assertValid(x1);
}