diff options
Diffstat (limited to 'src/history.cc')
-rw-r--r-- | src/history.cc | 455 |
1 files changed, 455 insertions, 0 deletions
diff --git a/src/history.cc b/src/history.cc new file mode 100644 index 00000000..d94ec647 --- /dev/null +++ b/src/history.cc @@ -0,0 +1,455 @@ +/* + * Copyright (c) 2003-2012, 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 "history.h" + +template <typename T> +struct f_max : public std::binary_function<T, T, bool> { + T operator()(const T& x, const T& y) const { + return std::max(x, y); + } +}; + +namespace ledger { + +template <typename EdgeWeightMap, + typename PricePointMap, + typename PriceRatioMap> +class recent_edge_weight +{ +public: + EdgeWeightMap weight; + PricePointMap price_point; + PriceRatioMap ratios; + + datetime_t reftime; + datetime_t oldest; + + recent_edge_weight() { } + recent_edge_weight(EdgeWeightMap _weight, + PricePointMap _price_point, + PriceRatioMap _ratios, + const datetime_t& _reftime, + const datetime_t& _oldest = datetime_t()) + : weight(_weight), price_point(_price_point), ratios(_ratios), + reftime(_reftime), oldest(_oldest) { } + + template <typename Edge> + bool operator()(const Edge& e) const + { +#if defined(DEBUG_ON) + DEBUG("history.find", " reftime = " << reftime); + if (! oldest.is_not_a_date_time()) { + DEBUG("history.find", " oldest = " << oldest); + } +#endif + + const price_map_t& prices(get(ratios, e)); + if (prices.empty()) { + DEBUG("history.find", " prices map is empty for this edge"); + return false; + } + + price_map_t::const_iterator low = prices.upper_bound(reftime); + if (low != prices.end() && low == prices.begin()) { + DEBUG("history.find", " don't use this edge"); + return false; + } else { + --low; + assert(((*low).first <= reftime)); + + if (! oldest.is_not_a_date_time() && (*low).first < oldest) { + DEBUG("history.find", " edge is out of range"); + return false; + } + + long secs = (reftime - (*low).first).total_seconds(); + assert(secs >= 0); + + put(weight, e, secs); + put(price_point, e, price_point_t((*low).first, (*low).second)); + + DEBUG("history.find", " using edge at price point " + << (*low).first << " " << (*low).second); + return true; + } + } +}; + +typedef filtered_graph + <commodity_history_t::Graph, + recent_edge_weight<commodity_history_t::EdgeWeightMap, + commodity_history_t::PricePointMap, + commodity_history_t::PriceRatioMap> > FGraph; + +typedef property_map<FGraph, vertex_name_t>::type FNameMap; +typedef property_map<FGraph, vertex_index_t>::type FIndexMap; +typedef iterator_property_map + <commodity_history_t::vertex_descriptor*, FIndexMap, + commodity_history_t::vertex_descriptor, + commodity_history_t::vertex_descriptor&> FPredecessorMap; +typedef iterator_property_map<long*, FIndexMap, long, long&> FDistanceMap; + +void commodity_history_t::add_commodity(commodity_t& comm) +{ + if (! comm.graph_index()) { + comm.set_graph_index(num_vertices(price_graph)); + add_vertex(/* vertex_name= */ &comm, price_graph); + } +} + +void commodity_history_t::add_price(const commodity_t& source, + const datetime_t& when, + const amount_t& price) +{ + assert(source != price.commodity()); + + vertex_descriptor sv = vertex(*source.graph_index(), price_graph); + vertex_descriptor tv = vertex(*price.commodity().graph_index(), price_graph); + + std::pair<edge_descriptor, bool> e1 = edge(sv, tv, price_graph); + if (! e1.second) + e1 = add_edge(sv, tv, price_graph); + + price_map_t& prices(get(ratiomap, e1.first)); + + std::pair<price_map_t::iterator, bool> result = + prices.insert(price_map_t::value_type(when, price)); + if (! result.second) { + // There is already an entry for this moment, so update it + (*result.first).second = price; + } +} + +void commodity_history_t::remove_price(const commodity_t& source, + const commodity_t& target, + const datetime_t& date) +{ + assert(source != target); + + vertex_descriptor sv = vertex(*source.graph_index(), price_graph); + vertex_descriptor tv = vertex(*target.graph_index(), price_graph); + + std::pair<Graph::edge_descriptor, bool> e1 = edge(sv, tv, price_graph); + if (e1.second) { + price_map_t& prices(get(ratiomap, e1.first)); + + // jww (2012-03-04): If it fails, should we give a warning? + prices.erase(date); + + if (prices.empty()) + remove_edge(e1.first, price_graph); + } +} + +void commodity_history_t::map_prices(function<void(datetime_t, + const amount_t&)> fn, + const commodity_t& source, + const datetime_t& moment, + const datetime_t& oldest, + bool bidirectionally) +{ + DEBUG("history.map", "Mapping prices for source commodity: " << source); + + vertex_descriptor sv = vertex(*source.graph_index(), price_graph); + + FGraph fg(price_graph, + recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap> + (get(edge_weight, price_graph), pricemap, ratiomap, + moment, oldest)); + + FNameMap namemap(get(vertex_name, fg)); + + graph_traits<FGraph>::adjacency_iterator f_vi, f_vend; + for (tie(f_vi, f_vend) = adjacent_vertices(sv, fg); f_vi != f_vend; ++f_vi) { + std::pair<Graph::edge_descriptor, bool> edgePair = edge(sv, *f_vi, fg); + Graph::edge_descriptor edge = edgePair.first; + + const price_map_t& prices(get(ratiomap, edge)); + + foreach (const price_map_t::value_type& pair, prices) { + const datetime_t& when(pair.first); + + DEBUG("history.map", "Price " << pair.second << " on " << when); + + if ((oldest.is_not_a_date_time() || when >= oldest) && when <= moment) { + if (pair.second.commodity() == source) { + if (bidirectionally) { + amount_t price(pair.second); + price.in_place_invert(); + if (source == *get(namemap, sv)) + price.set_commodity(const_cast<commodity_t&>(*get(namemap, *f_vi))); + else + price.set_commodity(const_cast<commodity_t&>(*get(namemap, sv))); + DEBUG("history.map", "Inverted price is " << price); + DEBUG("history.map", "fn(" << when << ", " << price << ")"); + fn(when, price); + } + } else { + DEBUG("history.map", "fn(" << when << ", " << pair.second << ")"); + fn(when, pair.second); + } + } + } + } +} + +optional<price_point_t> +commodity_history_t::find_price(const commodity_t& source, + const datetime_t& moment, + const datetime_t& oldest) +{ + vertex_descriptor sv = vertex(*source.graph_index(), price_graph); + + FGraph fg(price_graph, + recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap> + (get(edge_weight, price_graph), pricemap, ratiomap, + moment, oldest)); + + FNameMap namemap(get(vertex_name, fg)); + + DEBUG("history.find", "sv commodity = " << get(namemap, sv)->symbol()); +#if defined(DEBUG_ON) + if (source.has_flags(COMMODITY_PRIMARY)) + DEBUG("history.find", "sv commodity is primary"); +#endif + DEBUG("history.find", "tv commodity = none "); + + datetime_t most_recent = moment; + amount_t price; + + graph_traits<FGraph>::adjacency_iterator f_vi, f_vend; + for (tie(f_vi, f_vend) = adjacent_vertices(sv, fg); f_vi != f_vend; ++f_vi) { + std::pair<Graph::edge_descriptor, bool> edgePair = edge(sv, *f_vi, fg); + Graph::edge_descriptor edge = edgePair.first; + + DEBUG("history.find", "u commodity = " << get(namemap, sv)->symbol()); + DEBUG("history.find", "v commodity = " << get(namemap, *f_vi)->symbol()); + + const price_point_t& point(get(pricemap, edge)); + + if (price.is_null() || point.when > most_recent) { + most_recent = point.when; + price = point.price; + } + + DEBUG("history.find", "price was = " << price.unrounded()); + + if (price.commodity() == source) { + price.in_place_invert(); + if (source == *get(namemap, sv)) + price.set_commodity(const_cast<commodity_t&>(*get(namemap, *f_vi))); + else + price.set_commodity(const_cast<commodity_t&>(*get(namemap, sv))); + } + + DEBUG("history.find", "price is = " << price.unrounded()); + } + + if (price.is_null()) { + DEBUG("history.find", "there is no final price"); + return none; + } else { + DEBUG("history.find", "final price is = " << price.unrounded()); + return price_point_t(most_recent, price); + } +} + +optional<price_point_t> +commodity_history_t::find_price(const commodity_t& source, + const commodity_t& target, + const datetime_t& moment, + const datetime_t& oldest) +{ + assert(source != target); + + vertex_descriptor sv = vertex(*source.graph_index(), price_graph); + vertex_descriptor tv = vertex(*target.graph_index(), price_graph); + + FGraph fg(price_graph, + recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap> + (get(edge_weight, price_graph), pricemap, ratiomap, + moment, oldest)); + + FNameMap namemap(get(vertex_name, fg)); + + DEBUG("history.find", "sv commodity = " << get(namemap, sv)->symbol()); + DEBUG("history.find", "tv commodity = " << get(namemap, tv)->symbol()); + + std::size_t vector_len(num_vertices(fg)); + std::vector<vertex_descriptor> predecessors(vector_len); + std::vector<long> distances(vector_len); + + FPredecessorMap predecessorMap(&predecessors[0]); + FDistanceMap distanceMap(&distances[0]); + + dijkstra_shortest_paths(fg, /* start= */ sv, + predecessor_map(predecessorMap) + .distance_map(distanceMap) + .distance_combine(f_max<long>())); + + // Extract the shortest path and performance the calculations + datetime_t least_recent = moment; + amount_t price; + + const commodity_t * last_target = ⌖ + +#if defined(REVERSE_PREDECESSOR_MAP) + typedef tuple<const commodity_t *, const commodity_t *, + const price_point_t *> results_tuple; + std::vector<results_tuple> results; + bool results_reversed = false; +#endif + + vertex_descriptor v = tv; + for (vertex_descriptor u = predecessorMap[v]; + u != v; + v = u, u = predecessorMap[v]) + { + std::pair<Graph::edge_descriptor, bool> edgePair_uv = edge(u, v, fg); + std::pair<Graph::edge_descriptor, bool> edgePair_vu = edge(v, u, fg); + + Graph::edge_descriptor edge_uv = edgePair_uv.first; + Graph::edge_descriptor edge_vu = edgePair_vu.first; + + const price_point_t& point_uv(get(pricemap, edge_uv)); + const price_point_t& point_vu(get(pricemap, edge_vu)); + + const price_point_t& point(point_vu.when > point_uv.when ? + point_vu : point_uv); + + const commodity_t * u_comm = get(namemap, u); + const commodity_t * v_comm = get(namemap, v); + +#if defined(REVERSE_PREDECESSOR_MAP) + if (v == tv && u_comm != last_target && v_comm != last_target) + results_reversed = true; + + results.push_back(results_tuple(u_comm, v_comm, &point)); + } + + if (results_reversed) + std::reverse(results.begin(), results.end()); + + foreach (const results_tuple& edge, results) { + const commodity_t * u_comm = edge.get<0>(); + const commodity_t * v_comm = edge.get<1>(); + const price_point_t& point(*edge.get<2>()); +#else + assert(u_comm == last_target || v_comm == last_target); +#endif + + bool first_run = false; + if (price.is_null()) { + least_recent = point.when; + first_run = true; + } + else if (point.when < least_recent) { + least_recent = point.when; + } + + DEBUG("history.find", "u commodity = " << u_comm->symbol()); + DEBUG("history.find", "v commodity = " << v_comm->symbol()); + DEBUG("history.find", "last target = " << last_target->symbol()); + + // Determine which direction we are converting in + amount_t pprice(point.price); + DEBUG("history.find", "pprice = " << pprice.unrounded()); + + if (! first_run) { + DEBUG("history.find", "price was = " << price.unrounded()); + if (pprice.commodity_ptr() != last_target) + price *= pprice.inverted(); + else + price *= pprice; + } + else if (pprice.commodity_ptr() != last_target) { + price = pprice.inverted(); + } + else { + price = pprice; + } + DEBUG("history.find", "price is = " << price.unrounded()); + + if (last_target == v_comm) + last_target = u_comm; + else + last_target = v_comm; + + DEBUG("history.find", "last target now = " << last_target->symbol()); + } + + if (price.is_null()) { + DEBUG("history.find", "there is no final price"); + return none; + } else { + price.set_commodity(const_cast<commodity_t&>(target)); + DEBUG("history.find", "final price is = " << price.unrounded()); + + return price_point_t(least_recent, price); + } +} + +template <class Name> +class label_writer { +public: + label_writer(Name _name) : name(_name) { + TRACE_CTOR(label_writer<Name>, "Name"); + } + ~label_writer() throw() { + TRACE_DTOR(label_writer<Name>); + } + + template <class VertexOrEdge> + void operator()(std::ostream& out, const VertexOrEdge& v) const { + out << "[label=\"" << name[v]->symbol() << "\"]"; + } + +private: + Name name; +}; + +void commodity_history_t::print_map(std::ostream& out, const datetime_t& moment) +{ + if (moment.is_not_a_date_time()) { + write_graphviz(out, price_graph, + label_writer<NameMap>(get(vertex_name, price_graph))); + } else { + FGraph fg(price_graph, + recent_edge_weight<EdgeWeightMap, PricePointMap, PriceRatioMap> + (get(edge_weight, price_graph), pricemap, ratiomap, moment)); + write_graphviz(out, fg, label_writer<FNameMap>(get(vertex_name, fg))); + } +} + +} // namespace ledger |