Many fixes to --market and --exchange
Also, --exchange now accepted multiple, comma-separated commodities.
This commit is contained in:
parent
ea418c7fbc
commit
de6de07bac
8 changed files with 324 additions and 151 deletions
|
|
@ -515,13 +515,26 @@ amount_t::value(const bool primary_only,
|
||||||
const optional<commodity_t&>& in_terms_of) const
|
const optional<commodity_t&>& in_terms_of) const
|
||||||
{
|
{
|
||||||
if (quantity) {
|
if (quantity) {
|
||||||
|
#if defined(DEBUG_ON)
|
||||||
|
DEBUG("commodity.prices.find",
|
||||||
|
"amount_t::value of " << commodity().symbol());
|
||||||
|
if (moment)
|
||||||
|
DEBUG("commodity.prices.find",
|
||||||
|
"amount_t::value: moment = " << *moment);
|
||||||
|
if (in_terms_of)
|
||||||
|
DEBUG("commodity.prices.find",
|
||||||
|
"amount_t::value: in_terms_of = " << in_terms_of->symbol());
|
||||||
|
#endif
|
||||||
if (has_commodity() &&
|
if (has_commodity() &&
|
||||||
(! primary_only || commodity().has_flags(COMMODITY_PRIMARY)) &&
|
(! primary_only || ! commodity().has_flags(COMMODITY_PRIMARY))) {
|
||||||
(! in_terms_of || commodity() != *in_terms_of)) {
|
if (in_terms_of && commodity() == *in_terms_of) {
|
||||||
optional<price_point_t> point(commodity().find_price(in_terms_of, moment));
|
return *this;
|
||||||
if (point)
|
}
|
||||||
|
else if (optional<price_point_t> point =
|
||||||
|
commodity().find_price(in_terms_of, moment)) {
|
||||||
return (point->price * number()).rounded();
|
return (point->price * number()).rounded();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw_(amount_error, "Cannot determine value of an uninitialized amount");
|
throw_(amount_error, "Cannot determine value of an uninitialized amount");
|
||||||
}
|
}
|
||||||
|
|
@ -996,7 +1009,7 @@ void amount_t::print(std::ostream& _out) const
|
||||||
|
|
||||||
if (comm.annotated) {
|
if (comm.annotated) {
|
||||||
annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm));
|
annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm));
|
||||||
assert(&*ann.details.price != this);
|
assert(! ann.details.price || &*ann.details.price != this);
|
||||||
ann.write_annotations(out);
|
ann.write_annotations(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
168
src/commodity.cc
168
src/commodity.cc
|
|
@ -38,8 +38,9 @@ void commodity_t::base_t::history_t::add_price(commodity_t& source,
|
||||||
const amount_t& price,
|
const amount_t& price,
|
||||||
const bool reflexive)
|
const bool reflexive)
|
||||||
{
|
{
|
||||||
DEBUG("commodity.prices",
|
DEBUG("commodity.prices.add", "add_price to " << source
|
||||||
"add_price to " << source << " : " << date << ", " << price);
|
<< (reflexive ? " (secondary)" : " (primary)")
|
||||||
|
<< " : " << date << ", " << price);
|
||||||
|
|
||||||
history_map::iterator i = prices.find(date);
|
history_map::iterator i = prices.find(date);
|
||||||
if (i != prices.end()) {
|
if (i != prices.end()) {
|
||||||
|
|
@ -51,18 +52,19 @@ void commodity_t::base_t::history_t::add_price(commodity_t& source,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reflexive) {
|
if (reflexive) {
|
||||||
if (! price.commodity().has_flags(COMMODITY_NOMARKET)) {
|
|
||||||
amount_t inverse = price.inverted();
|
amount_t inverse = price.inverted();
|
||||||
inverse.set_commodity(const_cast<commodity_t&>(source));
|
inverse.set_commodity(const_cast<commodity_t&>(source));
|
||||||
price.commodity().add_price(date, inverse, false);
|
price.commodity().add_price(date, inverse, false);
|
||||||
}
|
} else {
|
||||||
|
DEBUG("commodity.prices.add",
|
||||||
|
"marking commodity " << source.symbol() << " as primary");
|
||||||
source.add_flags(COMMODITY_PRIMARY);
|
source.add_flags(COMMODITY_PRIMARY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool commodity_t::base_t::history_t::remove_price(const datetime_t& date)
|
bool commodity_t::base_t::history_t::remove_price(const datetime_t& date)
|
||||||
{
|
{
|
||||||
DEBUG("commodity.prices", "remove_price: " << date);
|
DEBUG("commodity.prices.add", "remove_price: " << date);
|
||||||
|
|
||||||
history_map::size_type n = prices.erase(date);
|
history_map::size_type n = prices.erase(date);
|
||||||
if (n > 0)
|
if (n > 0)
|
||||||
|
|
@ -93,7 +95,7 @@ void commodity_t::base_t::varied_history_t::
|
||||||
bool commodity_t::base_t::varied_history_t::remove_price(const datetime_t& date,
|
bool commodity_t::base_t::varied_history_t::remove_price(const datetime_t& date,
|
||||||
commodity_t& comm)
|
commodity_t& comm)
|
||||||
{
|
{
|
||||||
DEBUG("commodity.prices", "varied_remove_price: " << date << ", " << comm);
|
DEBUG("commodity.prices.add", "varied_remove_price: " << date << ", " << comm);
|
||||||
|
|
||||||
if (optional<history_t&> hist = history(comm))
|
if (optional<history_t&> hist = history(comm))
|
||||||
return hist->remove_price(date);
|
return hist->remove_price(date);
|
||||||
|
|
@ -120,22 +122,22 @@ optional<price_point_t>
|
||||||
} while (false)
|
} while (false)
|
||||||
|
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
if (moment)
|
if (moment)
|
||||||
DEBUG("commodity.prices", "find price nearest before or on: " << *moment);
|
DEBUG("commodity.prices.find", "find price nearest before or on: " << *moment);
|
||||||
else
|
else
|
||||||
DEBUG("commodity.prices", "find any price");
|
DEBUG("commodity.prices.find", "find any price");
|
||||||
|
|
||||||
if (oldest) {
|
if (oldest) {
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices", " but no older than: " << *oldest);
|
DEBUG("commodity.prices.find", " but no older than: " << *oldest);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (prices.size() == 0) {
|
if (prices.size() == 0) {
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices", " there are no prices in this history");
|
DEBUG("commodity.prices.find", " there are no prices in this history");
|
||||||
#endif
|
#endif
|
||||||
return none;
|
return none;
|
||||||
}
|
}
|
||||||
|
|
@ -146,8 +148,8 @@ optional<price_point_t>
|
||||||
point.price = (*r).second;
|
point.price = (*r).second;
|
||||||
found = true;
|
found = true;
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices", " using most recent price");
|
DEBUG("commodity.prices.find", " using most recent price");
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
history_map::const_iterator i = prices.lower_bound(*moment);
|
history_map::const_iterator i = prices.lower_bound(*moment);
|
||||||
|
|
@ -157,8 +159,8 @@ optional<price_point_t>
|
||||||
point.price = (*r).second;
|
point.price = (*r).second;
|
||||||
found = true;
|
found = true;
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices", " using last price");
|
DEBUG("commodity.prices.find", " using last price");
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
point.when = (*i).first;
|
point.when = (*i).first;
|
||||||
|
|
@ -174,8 +176,8 @@ optional<price_point_t>
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices", " using found price");
|
DEBUG("commodity.prices.find", " using found price");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -192,29 +194,29 @@ optional<price_point_t>
|
||||||
|
|
||||||
if (! found) {
|
if (! found) {
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices", " could not find a price");
|
DEBUG("commodity.prices.find", " could not find a price");
|
||||||
#endif
|
#endif
|
||||||
return none;
|
return none;
|
||||||
}
|
}
|
||||||
else if (moment && point.when > *moment) {
|
else if (moment && point.when > *moment) {
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices", " price is too young ");
|
DEBUG("commodity.prices.find", " price is too young ");
|
||||||
#endif
|
#endif
|
||||||
return none;
|
return none;
|
||||||
}
|
}
|
||||||
else if (oldest && point.when < *oldest) {
|
else if (oldest && point.when < *oldest) {
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices", " price is too old ");
|
DEBUG("commodity.prices.find", " price is too old ");
|
||||||
#endif
|
#endif
|
||||||
return none;
|
return none;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices",
|
DEBUG("commodity.prices.find",
|
||||||
" returning price: " << point.when << ", " << point.price);
|
" returning price: " << point.when << ", " << point.price);
|
||||||
#endif
|
#endif
|
||||||
return point;
|
return point;
|
||||||
|
|
@ -238,23 +240,23 @@ optional<price_point_t>
|
||||||
assert(! commodity || source != *commodity);
|
assert(! commodity || source != *commodity);
|
||||||
|
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices", "varied_find_price for: " << source);
|
DEBUG("commodity.prices.find", "varied_find_price for: " << source);
|
||||||
|
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
if (commodity)
|
if (commodity)
|
||||||
DEBUG("commodity.prices", " looking for: commodity '" << *commodity << "'");
|
DEBUG("commodity.prices.find", " looking for: commodity '" << *commodity << "'");
|
||||||
else
|
else
|
||||||
DEBUG("commodity.prices", " looking for: any commodity");
|
DEBUG("commodity.prices.find", " looking for: any commodity");
|
||||||
|
|
||||||
if (moment) {
|
if (moment) {
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices", " time index: " << *moment);
|
DEBUG("commodity.prices.find", " time index: " << *moment);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldest) {
|
if (oldest) {
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices", " only consider prices younger than: " << *oldest);
|
DEBUG("commodity.prices.find", " only consider prices younger than: " << *oldest);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -271,8 +273,8 @@ optional<price_point_t>
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent + 1);
|
DEBUG_INDENT("commodity.prices.find", indent + 1);
|
||||||
DEBUG("commodity.prices",
|
DEBUG("commodity.prices.find",
|
||||||
" searching for price via commodity '" << comm << "'");
|
" searching for price via commodity '" << comm << "'");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -288,8 +290,8 @@ optional<price_point_t>
|
||||||
|
|
||||||
if (commodity && comm != *commodity) {
|
if (commodity && comm != *commodity) {
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent + 1);
|
DEBUG_INDENT("commodity.prices.find", indent + 1);
|
||||||
DEBUG("commodity.prices", " looking for translation price");
|
DEBUG("commodity.prices.find", " looking for translation price");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
xlat = comm.find_price(commodity, moment, limit
|
xlat = comm.find_price(commodity, moment, limit
|
||||||
|
|
@ -299,8 +301,8 @@ optional<price_point_t>
|
||||||
);
|
);
|
||||||
if (xlat) {
|
if (xlat) {
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent + 1);
|
DEBUG_INDENT("commodity.prices.find", indent + 1);
|
||||||
DEBUG("commodity.prices", " found translated price "
|
DEBUG("commodity.prices.find", " found translated price "
|
||||||
<< xlat->price << " from " << xlat->when);
|
<< xlat->price << " from " << xlat->when);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -308,15 +310,15 @@ optional<price_point_t>
|
||||||
if (xlat->when < point->when) {
|
if (xlat->when < point->when) {
|
||||||
point->when = xlat->when;
|
point->when = xlat->when;
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent + 1);
|
DEBUG_INDENT("commodity.prices.find", indent + 1);
|
||||||
DEBUG("commodity.prices",
|
DEBUG("commodity.prices.find",
|
||||||
" adjusting date of result back to " << point->when);
|
" adjusting date of result back to " << point->when);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent + 1);
|
DEBUG_INDENT("commodity.prices.find", indent + 1);
|
||||||
DEBUG("commodity.prices", " saw no translated price there");
|
DEBUG("commodity.prices.find", " saw no translated price there");
|
||||||
#endif
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -324,8 +326,8 @@ optional<price_point_t>
|
||||||
|
|
||||||
assert(! commodity || point->price.commodity() == *commodity);
|
assert(! commodity || point->price.commodity() == *commodity);
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent + 1);
|
DEBUG_INDENT("commodity.prices.find", indent + 1);
|
||||||
DEBUG("commodity.prices",
|
DEBUG("commodity.prices.find",
|
||||||
" saw a price there: " << point->price << " from " << point->when);
|
" saw a price there: " << point->price << " from " << point->when);
|
||||||
#endif
|
#endif
|
||||||
if (! limit || point->when > *limit) {
|
if (! limit || point->when > *limit) {
|
||||||
|
|
@ -335,16 +337,16 @@ optional<price_point_t>
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent + 1);
|
DEBUG_INDENT("commodity.prices.find", indent + 1);
|
||||||
DEBUG("commodity.prices", " saw no price there");
|
DEBUG("commodity.prices.find", " saw no price there");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
#if defined(DEBUG_ON)
|
#if defined(DEBUG_ON)
|
||||||
DEBUG_INDENT("commodity.prices", indent);
|
DEBUG_INDENT("commodity.prices.find", indent);
|
||||||
DEBUG("commodity.prices",
|
DEBUG("commodity.prices.find",
|
||||||
" found price " << best.price << " from " << best.when);
|
" found price " << best.price << " from " << best.when);
|
||||||
#endif
|
#endif
|
||||||
return best;
|
return best;
|
||||||
|
|
@ -381,8 +383,7 @@ void commodity_t::exchange(commodity_t& commodity,
|
||||||
const amount_t& per_unit_cost,
|
const amount_t& per_unit_cost,
|
||||||
const datetime_t& moment)
|
const datetime_t& moment)
|
||||||
{
|
{
|
||||||
if (! commodity.has_flags(COMMODITY_NOMARKET)) {
|
DEBUG("commodity.prices.add", "exchanging commodity " << commodity
|
||||||
DEBUG("commodity.prices", "exchanging commodity " << commodity
|
|
||||||
<< " at per unit cost " << per_unit_cost << " on " << moment);
|
<< " at per unit cost " << per_unit_cost << " on " << moment);
|
||||||
|
|
||||||
commodity_t& base_commodity
|
commodity_t& base_commodity
|
||||||
|
|
@ -390,7 +391,6 @@ void commodity_t::exchange(commodity_t& commodity,
|
||||||
as_annotated_commodity(commodity).referent() : commodity);
|
as_annotated_commodity(commodity).referent() : commodity);
|
||||||
|
|
||||||
base_commodity.add_price(moment, per_unit_cost);
|
base_commodity.add_price(moment, per_unit_cost);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
commodity_t::cost_breakdown_t
|
commodity_t::cost_breakdown_t
|
||||||
|
|
@ -400,17 +400,12 @@ commodity_t::exchange(const amount_t& amount,
|
||||||
const optional<datetime_t>& moment,
|
const optional<datetime_t>& moment,
|
||||||
const optional<string>& tag)
|
const optional<string>& tag)
|
||||||
{
|
{
|
||||||
// (let* ((commodity (amount-commodity amount))
|
DEBUG("commodity.prices.add", "exchange: " << amount << " for " << cost);
|
||||||
// (current-annotation
|
DEBUG("commodity.prices.add", "exchange: is-per-unit = " << is_per_unit);
|
||||||
// (and (annotated-commodity-p commodity)
|
if (moment)
|
||||||
// (commodity-annotation commodity)))
|
DEBUG("commodity.prices.add", "exchange: moment = " << *moment);
|
||||||
// (base-commodity (if (annotated-commodity-p commodity)
|
if (tag)
|
||||||
// (get-referent commodity)
|
DEBUG("commodity.prices.add", "exchange: tag = " << *tag);
|
||||||
// commodity))
|
|
||||||
// (per-unit-cost (or per-unit-cost
|
|
||||||
// (divide total-cost amount)))
|
|
||||||
// (total-cost (or total-cost
|
|
||||||
// (multiply per-unit-cost amount))))
|
|
||||||
|
|
||||||
commodity_t& commodity(amount.commodity());
|
commodity_t& commodity(amount.commodity());
|
||||||
|
|
||||||
|
|
@ -418,44 +413,33 @@ commodity_t::exchange(const amount_t& amount,
|
||||||
if (commodity.annotated)
|
if (commodity.annotated)
|
||||||
current_annotation = &as_annotated_commodity(commodity).details;
|
current_annotation = &as_annotated_commodity(commodity).details;
|
||||||
|
|
||||||
commodity_t& base_commodity
|
amount_t per_unit_cost(is_per_unit ? cost : (cost / amount).unrounded());
|
||||||
(current_annotation ?
|
|
||||||
as_annotated_commodity(commodity).referent() : commodity);
|
|
||||||
|
|
||||||
amount_t per_unit_cost(is_per_unit ? cost : cost / amount);
|
DEBUG("commodity.prices.add", "exchange: per-unit-cost = " << per_unit_cost);
|
||||||
|
|
||||||
|
exchange(commodity, per_unit_cost, moment ? *moment : CURRENT_TIME());
|
||||||
|
|
||||||
cost_breakdown_t breakdown;
|
cost_breakdown_t breakdown;
|
||||||
breakdown.final_cost = ! is_per_unit ? cost : cost * amount;
|
breakdown.final_cost = ! is_per_unit ? cost : (cost * amount).unrounded();
|
||||||
|
|
||||||
// Add a price history entry for this conversion if we know when it took
|
DEBUG("commodity.prices.add",
|
||||||
// place
|
"exchange: final-cost = " << breakdown.final_cost);
|
||||||
|
|
||||||
// (if (and moment (not (commodity-no-market-price-p base-commodity)))
|
|
||||||
// (add-price base-commodity per-unit-cost moment))
|
|
||||||
|
|
||||||
if (moment && ! commodity.has_flags(COMMODITY_NOMARKET))
|
|
||||||
base_commodity.add_price(*moment, per_unit_cost);
|
|
||||||
|
|
||||||
// ;; returns: ANNOTATED-AMOUNT TOTAL-COST BASIS-COST
|
|
||||||
// (values (annotate-commodity
|
|
||||||
// amount
|
|
||||||
// (make-commodity-annotation :price per-unit-cost
|
|
||||||
// :date moment
|
|
||||||
// :tag tag))
|
|
||||||
// total-cost
|
|
||||||
// (if current-annotation
|
|
||||||
// (multiply (annotation-price current-annotation) amount)
|
|
||||||
// total-cost))))
|
|
||||||
|
|
||||||
if (current_annotation && current_annotation->price)
|
if (current_annotation && current_annotation->price)
|
||||||
breakdown.basis_cost = *current_annotation->price * amount;
|
breakdown.basis_cost = (*current_annotation->price * amount).unrounded();
|
||||||
else
|
else
|
||||||
breakdown.basis_cost = breakdown.final_cost;
|
breakdown.basis_cost = breakdown.final_cost;
|
||||||
|
|
||||||
|
DEBUG("commodity.prices.add",
|
||||||
|
"exchange: basis-cost = " << breakdown.basis_cost);
|
||||||
|
|
||||||
breakdown.amount =
|
breakdown.amount =
|
||||||
amount_t(amount, annotation_t(per_unit_cost, moment ?
|
amount_t(amount, annotation_t(per_unit_cost, moment ?
|
||||||
moment->date() : optional<date_t>(), tag));
|
moment->date() : optional<date_t>(), tag));
|
||||||
|
|
||||||
|
DEBUG("commodity.prices.add",
|
||||||
|
"exchange: amount = " << breakdown.amount);
|
||||||
|
|
||||||
return breakdown;
|
return breakdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -755,7 +739,7 @@ void annotated_commodity_t::write_annotations(std::ostream& out,
|
||||||
out << " {" << *info.price << '}';
|
out << " {" << *info.price << '}';
|
||||||
|
|
||||||
if (info.date)
|
if (info.date)
|
||||||
out << " [" << format_date(*info.date) << ']';
|
out << " [" << format_date(*info.date, string("%Y/%m/%d")) << ']';
|
||||||
|
|
||||||
if (info.tag)
|
if (info.tag)
|
||||||
out << " (" << *info.tag << ')';
|
out << " (" << *info.tag << ')';
|
||||||
|
|
|
||||||
99
src/entry.cc
99
src/entry.cc
|
|
@ -85,9 +85,6 @@ bool entry_base_t::finalize()
|
||||||
// for auto-calculating the value of entries with no cost, and the per-unit
|
// for auto-calculating the value of entries with no cost, and the per-unit
|
||||||
// price of unpriced commodities.
|
// price of unpriced commodities.
|
||||||
|
|
||||||
// (let ((balance 0)
|
|
||||||
// null-xact)
|
|
||||||
|
|
||||||
value_t balance;
|
value_t balance;
|
||||||
xact_t * null_xact = NULL;
|
xact_t * null_xact = NULL;
|
||||||
|
|
||||||
|
|
@ -119,8 +116,8 @@ bool entry_base_t::finalize()
|
||||||
|
|
||||||
DEBUG("entry.finalize", "initial balance = " << balance);
|
DEBUG("entry.finalize", "initial balance = " << balance);
|
||||||
|
|
||||||
// If there is only one xact, balance against the default account if
|
// If there is only one xact, balance against the default account if one has
|
||||||
// one has been set.
|
// been set.
|
||||||
|
|
||||||
if (journal && journal->basket && xacts.size() == 1 && ! balance.is_null()) {
|
if (journal && journal->basket && xacts.size() == 1 && ! balance.is_null()) {
|
||||||
// jww (2008-07-24): Need to make the rest of the code aware of what to do
|
// jww (2008-07-24): Need to make the rest of the code aware of what to do
|
||||||
|
|
@ -131,9 +128,9 @@ bool entry_base_t::finalize()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null_xact != NULL) {
|
if (null_xact != NULL) {
|
||||||
// If one xact has no value at all, its value will become the
|
// If one xact has no value at all, its value will become the inverse of
|
||||||
// inverse of the rest. If multiple commodities are involved, multiple
|
// the rest. If multiple commodities are involved, multiple xacts are
|
||||||
// xacts are generated to balance them all.
|
// generated to balance them all.
|
||||||
|
|
||||||
if (balance.is_balance()) {
|
if (balance.is_balance()) {
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
@ -161,41 +158,88 @@ bool entry_base_t::finalize()
|
||||||
else if (balance.is_balance() &&
|
else if (balance.is_balance() &&
|
||||||
balance.as_balance().amounts.size() == 2) {
|
balance.as_balance().amounts.size() == 2) {
|
||||||
// When an entry involves two different commodities (regardless of how
|
// When an entry involves two different commodities (regardless of how
|
||||||
// many xacts there are) determine the conversion ratio by dividing
|
// many xacts there are) determine the conversion ratio by dividing the
|
||||||
// the total value of one commodity by the total value of the other. This
|
// total value of one commodity by the total value of the other. This
|
||||||
// establishes the per-unit cost for this xact for both
|
// establishes the per-unit cost for this xact for both commodities.
|
||||||
// commodities.
|
|
||||||
|
|
||||||
|
DEBUG("entry.finalize", "there were exactly two commodities");
|
||||||
|
|
||||||
|
bool saw_cost = false;
|
||||||
|
xact_t * top_xact = NULL;
|
||||||
|
|
||||||
|
foreach (xact_t * xact, xacts) {
|
||||||
|
if (! xact->amount.is_null())
|
||||||
|
if (xact->amount.is_annotated())
|
||||||
|
top_xact = xact;
|
||||||
|
else if (! top_xact)
|
||||||
|
top_xact = xact;
|
||||||
|
|
||||||
|
if (xact->cost) {
|
||||||
|
saw_cost = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! saw_cost && top_xact) {
|
||||||
const balance_t& bal(balance.as_balance());
|
const balance_t& bal(balance.as_balance());
|
||||||
|
|
||||||
|
DEBUG("entry.finalize", "there were no costs, and a valid top_xact");
|
||||||
|
|
||||||
balance_t::amounts_map::const_iterator a = bal.amounts.begin();
|
balance_t::amounts_map::const_iterator a = bal.amounts.begin();
|
||||||
|
|
||||||
const amount_t& x((*a++).second);
|
const amount_t * x = &(*a++).second;
|
||||||
const amount_t& y((*a++).second);
|
const amount_t * y = &(*a++).second;
|
||||||
|
|
||||||
if (! y.is_realzero()) {
|
if (x->commodity() != top_xact->amount.commodity()) {
|
||||||
amount_t per_unit_cost = (x / y).abs();
|
const amount_t * t = x;
|
||||||
|
x = y;
|
||||||
|
y = t;
|
||||||
|
}
|
||||||
|
|
||||||
commodity_t& comm(x.commodity());
|
DEBUG("entry.finalize", "primary amount = " << *y);
|
||||||
|
DEBUG("entry.finalize", "secondary amount = " << *x);
|
||||||
|
|
||||||
|
commodity_t& comm(x->commodity());
|
||||||
|
amount_t per_unit_cost;
|
||||||
|
amount_t total_cost;
|
||||||
|
|
||||||
|
foreach (xact_t * xact, xacts) {
|
||||||
|
if (xact != top_xact && xact->must_balance() &&
|
||||||
|
! xact->amount.is_null() &&
|
||||||
|
xact->amount.is_annotated() &&
|
||||||
|
xact->amount.annotation().price) {
|
||||||
|
amount_t temp = *xact->amount.annotation().price * xact->amount;
|
||||||
|
if (total_cost.is_null()) {
|
||||||
|
total_cost = temp;
|
||||||
|
y = &total_cost;
|
||||||
|
} else {
|
||||||
|
total_cost += temp;
|
||||||
|
}
|
||||||
|
DEBUG("entry.finalize", "total_cost = " << total_cost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
per_unit_cost = (*y / *x).abs();
|
||||||
|
|
||||||
|
DEBUG("entry.finalize", "per_unit_cost = " << per_unit_cost);
|
||||||
|
|
||||||
foreach (xact_t * xact, xacts) {
|
foreach (xact_t * xact, xacts) {
|
||||||
const amount_t& amt(xact->amount);
|
const amount_t& amt(xact->amount);
|
||||||
|
|
||||||
if (! (xact->cost || ! xact->must_balance() ||
|
if (xact->must_balance() && amt.commodity() == comm) {
|
||||||
amt.commodity() == comm)) {
|
|
||||||
balance -= amt;
|
balance -= amt;
|
||||||
xact->cost = per_unit_cost * amt;
|
xact->cost = per_unit_cost * amt;
|
||||||
balance += *xact->cost;
|
balance += *xact->cost;
|
||||||
}
|
|
||||||
|
|
||||||
|
DEBUG("entry.finalize", "set xact->cost to = " << *xact->cost);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG("entry.finalize", "resolved balance = " << balance);
|
DEBUG("entry.finalize", "resolved balance = " << balance);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that the xact list has its final form, calculate the balance
|
// Now that the xact list has its final form, calculate the balance once
|
||||||
// once more in terms of total cost, accounting for any possible gain/loss
|
// more in terms of total cost, accounting for any possible gain/loss
|
||||||
// amounts.
|
// amounts.
|
||||||
|
|
||||||
foreach (xact_t * xact, xacts) {
|
foreach (xact_t * xact, xacts) {
|
||||||
|
|
@ -204,9 +248,12 @@ bool entry_base_t::finalize()
|
||||||
throw_(balance_error, "Transaction's cost must be of a different commodity");
|
throw_(balance_error, "Transaction's cost must be of a different commodity");
|
||||||
|
|
||||||
commodity_t::cost_breakdown_t breakdown =
|
commodity_t::cost_breakdown_t breakdown =
|
||||||
commodity_t::exchange(xact->amount, *xact->cost);
|
commodity_t::exchange(xact->amount, *xact->cost, false,
|
||||||
|
datetime_t(date(), time_duration(0, 0, 0, 0)));
|
||||||
|
|
||||||
if (xact->amount.is_annotated())
|
if (xact->amount.is_annotated() &&
|
||||||
|
breakdown.basis_cost.commodity() ==
|
||||||
|
breakdown.final_cost.commodity())
|
||||||
add_or_set_value(balance, (breakdown.basis_cost -
|
add_or_set_value(balance, (breakdown.basis_cost -
|
||||||
breakdown.final_cost).rounded());
|
breakdown.final_cost).rounded());
|
||||||
else
|
else
|
||||||
|
|
@ -231,8 +278,8 @@ bool entry_base_t::finalize()
|
||||||
if (! xact->amount.is_null()) {
|
if (! xact->amount.is_null()) {
|
||||||
all_null = false;
|
all_null = false;
|
||||||
|
|
||||||
// jww (2008-08-09): For now, this feature only works for
|
// jww (2008-08-09): For now, this feature only works for non-specific
|
||||||
// non-specific commodities.
|
// commodities.
|
||||||
add_or_set_value(xact->account->xdata().value, xact->amount);
|
add_or_set_value(xact->account->xdata().value, xact->amount);
|
||||||
|
|
||||||
DEBUG("entry.finalize.totals",
|
DEBUG("entry.finalize.totals",
|
||||||
|
|
|
||||||
|
|
@ -128,26 +128,22 @@ value_t report_t::fn_market_value(call_scope_t& scope)
|
||||||
scoped_array<char> buf(new char[args.get<string>(2).length() + 1]);
|
scoped_array<char> buf(new char[args.get<string>(2).length() + 1]);
|
||||||
std::strcpy(buf.get(), args.get<string>(2).c_str());
|
std::strcpy(buf.get(), args.get<string>(2).c_str());
|
||||||
|
|
||||||
bool primary_only = false;
|
|
||||||
for (char * p = std::strtok(buf.get(), ",");
|
for (char * p = std::strtok(buf.get(), ",");
|
||||||
p;
|
p;
|
||||||
p = std::strtok(NULL, ",")) {
|
p = std::strtok(NULL, ",")) {
|
||||||
if (commodity_t * commodity = amount_t::current_pool->find(trim_ws(p))) {
|
if (commodity_t * commodity = amount_t::current_pool->find(trim_ws(p))) {
|
||||||
value_t result =
|
value_t result =
|
||||||
args.value_at(0).value(primary_only, args.has(1) ?
|
args.value_at(0).value(false, args.has(1) ?
|
||||||
args.get<datetime_t>(1) :
|
args.get<datetime_t>(1) :
|
||||||
optional<datetime_t>(), *commodity);
|
optional<datetime_t>(), *commodity);
|
||||||
if (! result.is_null())
|
if (! result.is_null())
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// For subsequent, secondary commodities, don't convert primaries
|
|
||||||
primary_only = true;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
value_t result =
|
value_t result =
|
||||||
args.value_at(0).value(! args.has(2), args.has(1) ?
|
args.value_at(0).value(true, args.has(1) ?
|
||||||
args.get<datetime_t>(1) :
|
args.get<datetime_t>(1) : optional<datetime_t>());
|
||||||
optional<datetime_t>());
|
|
||||||
if (! result.is_null())
|
if (! result.is_null())
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -411,9 +411,9 @@ public:
|
||||||
OPTION_(report_t, market, DO() { // -V
|
OPTION_(report_t, market, DO() { // -V
|
||||||
parent->HANDLER(revalued).on_only();
|
parent->HANDLER(revalued).on_only();
|
||||||
parent->HANDLER(display_amount_)
|
parent->HANDLER(display_amount_)
|
||||||
.set_expr("market(amount_expr, now, exchange)");
|
.set_expr("market(amount_expr, date, exchange)");
|
||||||
parent->HANDLER(display_total_)
|
parent->HANDLER(display_total_)
|
||||||
.set_expr("market(total_expr, now, exchange)");
|
.set_expr("market(total_expr, date, exchange)");
|
||||||
});
|
});
|
||||||
|
|
||||||
OPTION_(report_t, monthly, DO() { // -M
|
OPTION_(report_t, monthly, DO() { // -M
|
||||||
|
|
|
||||||
|
|
@ -492,7 +492,7 @@ void instance_t::price_entry_directive(char * line)
|
||||||
|
|
||||||
if (commodity_t * commodity =
|
if (commodity_t * commodity =
|
||||||
amount_t::current_pool->find_or_create(symbol)) {
|
amount_t::current_pool->find_or_create(symbol)) {
|
||||||
commodity->add_price(datetime, price);
|
commodity->add_price(datetime, price, true);
|
||||||
commodity->add_flags(COMMODITY_KNOWN);
|
commodity->add_flags(COMMODITY_KNOWN);
|
||||||
} else {
|
} else {
|
||||||
assert(false);
|
assert(false);
|
||||||
|
|
@ -895,19 +895,11 @@ xact_t * instance_t::parse_xact(char * line,
|
||||||
if (xact->cost->sign() < 0)
|
if (xact->cost->sign() < 0)
|
||||||
throw parse_error("A transaction's cost may not be negative");
|
throw parse_error("A transaction's cost may not be negative");
|
||||||
|
|
||||||
amount_t per_unit_cost(*xact->cost);
|
|
||||||
if (per_unit)
|
if (per_unit)
|
||||||
*xact->cost *= xact->amount;
|
*xact->cost *= xact->amount;
|
||||||
else
|
|
||||||
per_unit_cost /= xact->amount;
|
|
||||||
|
|
||||||
commodity_t::exchange(xact->amount.commodity(),
|
|
||||||
per_unit_cost, datetime_t(xact->date()));
|
|
||||||
|
|
||||||
DEBUG("textual.parse", "line " << linenum << ": "
|
DEBUG("textual.parse", "line " << linenum << ": "
|
||||||
<< "Total cost is " << *xact->cost);
|
<< "Total cost is " << *xact->cost);
|
||||||
DEBUG("textual.parse", "line " << linenum << ": "
|
|
||||||
<< "Per-unit cost is " << per_unit_cost);
|
|
||||||
DEBUG("textual.parse", "line " << linenum << ": "
|
DEBUG("textual.parse", "line " << linenum << ": "
|
||||||
<< "Annotated amount is " << xact->amount);
|
<< "Annotated amount is " << xact->amount);
|
||||||
|
|
||||||
|
|
|
||||||
81
test/baseline/opt-exchange.test
Normal file
81
test/baseline/opt-exchange.test
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
reg --exchange=' C, A '
|
||||||
|
<<<
|
||||||
|
2009/01/01 January 1st, 2009 (1)
|
||||||
|
Assets:Brokerage 100 A
|
||||||
|
Assets:Brokerage -200 B
|
||||||
|
|
||||||
|
2009/01/01 January 1st, 2009 (2)
|
||||||
|
Assets:Brokerage 100 A
|
||||||
|
Assets:Brokerage -300 B
|
||||||
|
|
||||||
|
2009/01/01 January 1st, 2009 (3)
|
||||||
|
Assets:Brokerage 100 A
|
||||||
|
Assets:Brokerage -400 B
|
||||||
|
|
||||||
|
2009/01/02 January 2nd, 2009
|
||||||
|
Assets:Brokerage 250 B
|
||||||
|
Assets:Brokerage -500 C
|
||||||
|
|
||||||
|
2009/01/03 January 3rd, 2009
|
||||||
|
Assets:Brokerage 600 C
|
||||||
|
Assets:Brokerage -1000 D
|
||||||
|
|
||||||
|
2009/01/04 January 4th, 2009
|
||||||
|
Assets:Brokerage 300 A
|
||||||
|
Assets:Brokerage -15000 F
|
||||||
|
|
||||||
|
2009/01/05 January 5th, 2009
|
||||||
|
Assets:Brokerage 2000 E
|
||||||
|
Assets:Brokerage -8000 F
|
||||||
|
|
||||||
|
2009/01/06 January 6th, 2009
|
||||||
|
Assets:Brokerage 155 A @ 2 D
|
||||||
|
Assets:Brokerage
|
||||||
|
|
||||||
|
2009/01/07 January 7th, 2009
|
||||||
|
Assets:Brokerage 155 A @@ 200 C
|
||||||
|
Assets:Brokerage
|
||||||
|
|
||||||
|
2009/01/08 January 8th, 2009
|
||||||
|
Assets:Brokerage 155 A (A123) @@ 500 F
|
||||||
|
Assets:Brokerage
|
||||||
|
|
||||||
|
2009/01/09 January 9th, 2009
|
||||||
|
Assets:Brokerage 1000.00 E
|
||||||
|
Assets:Brokerage -155 A {2 D}
|
||||||
|
|
||||||
|
2009/01/10 January 10th, 2009
|
||||||
|
Assets:Brokerage $2,000.00
|
||||||
|
Assets:Brokerage -155 A [2009/01/06]
|
||||||
|
>>>1
|
||||||
|
09-Jan-01 January 1st, 2009 (1) Assets:Brokerage 100 A 100 A
|
||||||
|
Assets:Brokerage -50 A 100 A
|
||||||
|
-200 B
|
||||||
|
09-Jan-01 January 1st, 2009 (2) Assets:Brokerage 100 A 200 A
|
||||||
|
-200 B
|
||||||
|
Assets:Brokerage -75 A 200 A
|
||||||
|
-500 B
|
||||||
|
09-Jan-01 January 1st, 2009 (3) Assets:Brokerage 100 A 300 A
|
||||||
|
-500 B
|
||||||
|
Assets:Brokerage -100 A 300 A
|
||||||
|
-900 B
|
||||||
|
09-Jan-02 January 2nd, 2009 Assets:Brokerage 500 C 1100 C
|
||||||
|
Assets:Brokerage -500 C 600 C
|
||||||
|
09-Jan-03 January 3rd, 2009 Assets:Brokerage 600 C 1200 C
|
||||||
|
Assets:Brokerage -600 C 600 C
|
||||||
|
09-Jan-04 January 4th, 2009 Assets:Brokerage 2400 C 3000 C
|
||||||
|
Assets:Brokerage -2400 C 600 C
|
||||||
|
09-Jan-05 January 5th, 2009 Assets:Brokerage 1280 C 1880 C
|
||||||
|
Assets:Brokerage -1280 C 600 C
|
||||||
|
09-Jan-06 January 6th, 2009 Assets:Brokerage 186 C -1254 C
|
||||||
|
Assets:Brokerage -186 C -1440 C
|
||||||
|
09-Jan-07 January 7th, 2009 Assets:Brokerage 200 C -1258 C
|
||||||
|
Assets:Brokerage -200 C -1458 C
|
||||||
|
09-Jan-08 January 8th, 2009 Assets:Brokerage 200 C -6871 C
|
||||||
|
Assets:Brokerage -200 C -7071 C
|
||||||
|
09-Jan-09 January 9th, 2009 Assets:Brokerage 200 C -9671 C
|
||||||
|
Assets:Brokerage -200 C -9871 C
|
||||||
|
09-Jan-10 January 10th, 2009 Assets:Brokerage 200 C -9671 C
|
||||||
|
Assets:Brokerage -200 C -9871 C
|
||||||
|
>>>2
|
||||||
|
=== 0
|
||||||
60
test/baseline/opt-market.test
Normal file
60
test/baseline/opt-market.test
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
reg --market stocks
|
||||||
|
<<<
|
||||||
|
2009/01/01 Sample 1a
|
||||||
|
Assets:Brokerage:Stocks 100 S
|
||||||
|
Assets:Brokerage:Cash -100 P
|
||||||
|
|
||||||
|
P 2009/01/01 00:00:00 S 2 P
|
||||||
|
|
||||||
|
2009/02/01 Sample 2a
|
||||||
|
Assets:Brokerage:Stocks 100 S @ 1 P
|
||||||
|
Assets:Brokerage:Cash
|
||||||
|
|
||||||
|
P 2009/02/01 00:00:00 S 4 P
|
||||||
|
|
||||||
|
2009/03/01 Sample 3a
|
||||||
|
Assets:Brokerage:Stocks 100 S @@ 100 P
|
||||||
|
Assets:Brokerage:Cash
|
||||||
|
|
||||||
|
P 2009/03/01 00:00:00 S 8 P
|
||||||
|
|
||||||
|
2009/04/01 Sample 4a
|
||||||
|
Assets:Brokerage:Cash 100 P
|
||||||
|
Assets:Brokerage:Stocks -100 S {1 P}
|
||||||
|
|
||||||
|
P 2009/04/01 00:00:00 S 16 P
|
||||||
|
|
||||||
|
; In this usage case, the top amount is always secondary
|
||||||
|
; 2010/01/01 Sample 1b
|
||||||
|
; Assets:Brokerage:Cash -100 P
|
||||||
|
; Assets:Brokerage:Stocks 100 S
|
||||||
|
;
|
||||||
|
; P 2010/01/01 00:00:00 S 2 P
|
||||||
|
|
||||||
|
2010/02/01 Sample 2b
|
||||||
|
Assets:Brokerage:Cash
|
||||||
|
Assets:Brokerage:Stocks 100 S @ 1 P
|
||||||
|
|
||||||
|
P 2010/02/01 00:00:00 S 4 P
|
||||||
|
|
||||||
|
2010/03/01 Sample 3b
|
||||||
|
Assets:Brokerage:Cash
|
||||||
|
Assets:Brokerage:Stocks 100 S @@ 100 P
|
||||||
|
|
||||||
|
P 2010/03/01 00:00:00 S 8 P
|
||||||
|
|
||||||
|
2010/04/01 Sample 4b
|
||||||
|
Assets:Brokerage:Stocks -100 S {1 P}
|
||||||
|
Assets:Brokerage:Cash 100 P
|
||||||
|
|
||||||
|
P 2010/04/01 00:00:00 S 16 P
|
||||||
|
>>>1
|
||||||
|
09-Jan-01 Sample 1a As:Brokerage:Stocks 200 P 200 P
|
||||||
|
09-Feb-01 Sample 2a As:Brokerage:Stocks 400 P 800 P
|
||||||
|
09-Mar-01 Sample 3a As:Brokerage:Stocks 800 P 2400 P
|
||||||
|
09-Apr-01 Sample 4a As:Brokerage:Stocks -1600 P 3200 P
|
||||||
|
10-Feb-01 Sample 2b As:Brokerage:Stocks 400 P 1200 P
|
||||||
|
10-Mar-01 Sample 3b As:Brokerage:Stocks 800 P 3200 P
|
||||||
|
10-Apr-01 Sample 4b As:Brokerage:Stocks -1600 P 4800 P
|
||||||
|
>>>2
|
||||||
|
=== 0
|
||||||
Loading…
Add table
Reference in a new issue