mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
[price quotes] Add report member function to display quote information to stdout.
Instead of creating price instances in the database.
This commit is contained in:
parent
6ffb0bb633
commit
d97ea77762
@ -92,6 +92,7 @@ public:
|
|||||||
void fetch (QofBook *book);
|
void fetch (QofBook *book);
|
||||||
void fetch (CommVec& commodities);
|
void fetch (CommVec& commodities);
|
||||||
void fetch (gnc_commodity *comm);
|
void fetch (gnc_commodity *comm);
|
||||||
|
void report (const char* source, const StrVec& commodities, bool verbose);
|
||||||
|
|
||||||
const std::string& version() noexcept { return m_version.empty() ? not_found : m_version; }
|
const std::string& version() noexcept { return m_version.empty() ? not_found : m_version; }
|
||||||
const QuoteSources& sources() noexcept { return m_sources; }
|
const QuoteSources& sources() noexcept { return m_sources; }
|
||||||
@ -101,8 +102,10 @@ public:
|
|||||||
std::string report_failures() noexcept;
|
std::string report_failures() noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::string query_fq (const char* source, const StrVec& commoditites);
|
||||||
std::string query_fq (const CommVec&);
|
std::string query_fq (const CommVec&);
|
||||||
void parse_quotes (const std::string& quote_str, const CommVec& commodities);
|
bpt::ptree parse_quotes (const std::string& quote_str);
|
||||||
|
void create_quotes(const bpt::ptree& pt, const CommVec& comm_vec);
|
||||||
std::string comm_vec_to_json_string(const CommVec&) const;
|
std::string comm_vec_to_json_string(const CommVec&) const;
|
||||||
GNCPrice* parse_one_quote(const bpt::ptree&, gnc_commodity*);
|
GNCPrice* parse_one_quote(const bpt::ptree&, gnc_commodity*);
|
||||||
|
|
||||||
@ -133,6 +136,9 @@ private:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void show_quotes(const bpt::ptree& pt, const StrVec& commodities, bool verbose);
|
||||||
|
static void show_currency_quotes(const bpt::ptree& pt, const StrVec& commodities, bool verbose);
|
||||||
|
|
||||||
static const std::string empty_string{};
|
static const std::string empty_string{};
|
||||||
|
|
||||||
GncFQQuoteSource::GncFQQuoteSource() :
|
GncFQQuoteSource::GncFQQuoteSource() :
|
||||||
@ -293,9 +299,42 @@ GncQuotesImpl::fetch (CommVec& commodities)
|
|||||||
m_failures.clear();
|
m_failures.clear();
|
||||||
if (commodities.empty())
|
if (commodities.empty())
|
||||||
return;
|
return;
|
||||||
|
try
|
||||||
|
{
|
||||||
auto quote_str{query_fq (commodities)};
|
auto quote_str{query_fq (commodities)};
|
||||||
parse_quotes (quote_str, commodities);
|
auto ptree{parse_quotes (quote_str)};
|
||||||
|
create_quotes(ptree, commodities);
|
||||||
|
}
|
||||||
|
catch (const GncQuoteException& err)
|
||||||
|
{
|
||||||
|
std::cerr << _("Finance::Quote retrieval failed with error ") << err.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GncQuotesImpl::report (const char* source, const StrVec& commodities,
|
||||||
|
bool verbose)
|
||||||
|
{
|
||||||
|
bool is_currency{source && strcmp(source, "currency") == 0};
|
||||||
|
m_failures.clear();
|
||||||
|
if (commodities.empty())
|
||||||
|
{
|
||||||
|
std::cerr << _("There were no commodities for which to retrieve quotes.") << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto quote_str{query_fq (source, commodities)};
|
||||||
|
auto ptree{parse_quotes (quote_str)};
|
||||||
|
if (is_currency)
|
||||||
|
show_currency_quotes(ptree, commodities, verbose);
|
||||||
|
else
|
||||||
|
show_quotes(ptree, commodities, verbose);
|
||||||
|
}
|
||||||
|
catch (const GncQuoteException& err)
|
||||||
|
{
|
||||||
|
std::cerr << _("Finance::Quote retrieval failed with error ") << err.what() << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const QFVec&
|
const QFVec&
|
||||||
@ -388,11 +427,10 @@ GncQuotesImpl::comm_vec_to_json_string (const CommVec& comm_vec) const
|
|||||||
return result.str();
|
return result.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
static inline std::string
|
||||||
GncQuotesImpl::query_fq (const CommVec& comm_vec)
|
get_quotes(const std::string& json_str, const std::unique_ptr<GncQuoteSource>& qs)
|
||||||
{
|
{
|
||||||
auto json_str{comm_vec_to_json_string(comm_vec)};
|
auto [rv, quotes, errors] = qs->get_quotes(json_str);
|
||||||
auto [rv, quotes, errors] = m_quotesource->get_quotes(json_str);
|
|
||||||
std::string answer;
|
std::string answer;
|
||||||
|
|
||||||
if (rv == 0)
|
if (rv == 0)
|
||||||
@ -408,13 +446,47 @@ GncQuotesImpl::query_fq (const CommVec& comm_vec)
|
|||||||
throw(GncQuoteException(err_str));
|
throw(GncQuoteException(err_str));
|
||||||
}
|
}
|
||||||
|
|
||||||
return answer;
|
|
||||||
// for (auto line : quotes)
|
// for (auto line : quotes)
|
||||||
// PINFO("Output line retrieved from wrapper:\n%s", line.c_str());
|
// PINFO("Output line retrieved from wrapper:\n%s", line.c_str());
|
||||||
//
|
//
|
||||||
// for (auto line : errors)
|
// for (auto line : errors)
|
||||||
// PINFO("Error line retrieved from wrapper:\n%s",line.c_str());˚
|
// PINFO("Error line retrieved from wrapper:\n%s",line.c_str());˚
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
GncQuotesImpl::query_fq (const char* source, const StrVec& commodities)
|
||||||
|
{
|
||||||
|
bpt::ptree pt;
|
||||||
|
auto is_currency{strcmp(source, "currency") == 0};
|
||||||
|
|
||||||
|
if (is_currency && commodities.size() < 2)
|
||||||
|
throw(GncQuoteException(_("Currency quotes requires at least two currencies")));
|
||||||
|
|
||||||
|
if (is_currency)
|
||||||
|
pt.put("defaultcurrency", commodities[0].c_str());
|
||||||
|
else
|
||||||
|
pt.put("defaultcurrency", gnc_commodity_get_mnemonic(m_dflt_curr));
|
||||||
|
|
||||||
|
std::for_each(is_currency ? ++commodities.cbegin() : commodities.cbegin(),
|
||||||
|
commodities.cend(),
|
||||||
|
[this, source, &pt](auto sym)
|
||||||
|
{
|
||||||
|
std::string key{source};
|
||||||
|
key += "." + sym;
|
||||||
|
pt.put(key, "");
|
||||||
|
});
|
||||||
|
std::ostringstream result;
|
||||||
|
bpt::write_json(result, pt);
|
||||||
|
return get_quotes(result.str(), m_quotesource);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
GncQuotesImpl::query_fq (const CommVec& comm_vec)
|
||||||
|
{
|
||||||
|
auto json_str{comm_vec_to_json_string(comm_vec)};
|
||||||
|
return get_quotes(json_str, m_quotesource);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PriceParams
|
struct PriceParams
|
||||||
@ -636,8 +708,8 @@ GncQuotesImpl::parse_one_quote(const bpt::ptree& pt, gnc_commodity* comm)
|
|||||||
return gnc_price;
|
return gnc_price;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bpt::ptree
|
||||||
GncQuotesImpl::parse_quotes (const std::string& quote_str, const CommVec& comm_vec)
|
GncQuotesImpl::parse_quotes (const std::string& quote_str)
|
||||||
{
|
{
|
||||||
bpt::ptree pt;
|
bpt::ptree pt;
|
||||||
std::istringstream ss {quote_str};
|
std::istringstream ss {quote_str};
|
||||||
@ -671,7 +743,12 @@ GncQuotesImpl::parse_quotes (const std::string& quote_str, const CommVec& comm_v
|
|||||||
error_msg += what;
|
error_msg += what;
|
||||||
throw(GncQuoteException(error_msg));
|
throw(GncQuoteException(error_msg));
|
||||||
}
|
}
|
||||||
|
return pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GncQuotesImpl::create_quotes (const bpt::ptree& pt, const CommVec& comm_vec)
|
||||||
|
{
|
||||||
auto pricedb{gnc_pricedb_get_db(m_book)};
|
auto pricedb{gnc_pricedb_get_db(m_book)};
|
||||||
for (auto comm : comm_vec)
|
for (auto comm : comm_vec)
|
||||||
{
|
{
|
||||||
@ -685,8 +762,134 @@ GncQuotesImpl::parse_quotes (const std::string& quote_str, const CommVec& comm_v
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
show_verbose_quote(const bpt::ptree& comm_pt)
|
||||||
|
{
|
||||||
|
std::for_each(comm_pt.begin(), comm_pt.end(),
|
||||||
|
[](auto elem) {
|
||||||
|
std::cout << std::setw(12) << std::right << elem.first << " => " <<
|
||||||
|
std::left << elem.second.data() << "\n";
|
||||||
|
});
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
show_gnucash_quote(const bpt::ptree& comm_pt)
|
||||||
|
{
|
||||||
|
constexpr const char* ptr{"<=== "};
|
||||||
|
constexpr const char* dptr{"<=\\ "};
|
||||||
|
constexpr const char* uptr{"<=/ "};
|
||||||
|
//Translators: Means that the preceding element is required
|
||||||
|
const char* reqd{C_("Finance::Quote", "required")};
|
||||||
|
//Translators: Means that the quote will work best if the preceding element is provided
|
||||||
|
const char* rec{C_("Finance::Quote", "recommended")};
|
||||||
|
//Translators: Means that one of the indicated elements is required
|
||||||
|
const char* oot{C_("Finance::Quote", "one of these")};
|
||||||
|
//Translators: Means that the preceding element is optional
|
||||||
|
const char* opt{C_("Finance::Quote", "optional")};
|
||||||
|
//Translators: Means that a required element wasn't reported. The *s are for emphasis.
|
||||||
|
const char* miss{C_("Finance::Quote", "**missing**")};
|
||||||
|
|
||||||
|
const std::string miss_str{miss};
|
||||||
|
auto outline{[](const char* label, std::string value, const char* pointer, const char* req) {
|
||||||
|
std::cout << std::setw(12) << std::right << label << std::setw(16) << std::left <<
|
||||||
|
value << pointer << req << "\n";
|
||||||
|
}};
|
||||||
|
std::cout << _("Finance::Quote fields GnuCash uses:") << "\n";
|
||||||
|
//Translators: The stock or Mutual Fund symbol, ISIN, CUSIP, etc.
|
||||||
|
outline(C_("Finance::Quote", "symbol: "), comm_pt.get<char>("symbol", miss), ptr, reqd);
|
||||||
|
//Translators: The date of the quote.
|
||||||
|
outline(C_("Finance::Quote", "date: "), comm_pt.get<char>("date", miss), ptr, rec);
|
||||||
|
//Translators: The quote currency
|
||||||
|
outline(C_("Finance::Quote", "currency: "), comm_pt.get<char>("currency", miss), ptr, reqd);
|
||||||
|
auto last{comm_pt.get<char>("last", "")};
|
||||||
|
auto nav{comm_pt.get<char>("nav", "")};
|
||||||
|
auto price{comm_pt.get<char>("nav", "")};
|
||||||
|
auto no_price{last.empty() && nav.empty() && price.empty()};
|
||||||
|
//Translators: The quote is for the most recent trade on the exchange
|
||||||
|
outline(C_("Finance::Quote", "last: "), no_price ? miss_str : last, dptr, "");
|
||||||
|
//Translators: The quote is for an open-ended mutual fund and represents the net asset value of one unit of the fund at the previous close of trading.
|
||||||
|
outline(C_("Finance::Quote", "nav: "), no_price ? miss_str : nav, ptr, oot);
|
||||||
|
//Translators: The quote is neither a last trade nor an NAV.
|
||||||
|
outline(C_("Finance::Quote", "price: "), no_price ? miss_str : price, uptr, "");
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
static const bpt::ptree empty_tree{};
|
||||||
|
|
||||||
|
static inline const bpt::ptree&
|
||||||
|
get_commodity_data(const bpt::ptree& pt, const std::string& comm)
|
||||||
|
{
|
||||||
|
auto commdata{pt.find(comm)};
|
||||||
|
if (commdata == pt.not_found())
|
||||||
|
{
|
||||||
|
std::cout << comm << " " << _("Finance::Quote returned no data and set no error.") << std::endl;
|
||||||
|
return empty_tree;
|
||||||
|
}
|
||||||
|
auto& comm_pt{commdata->second};
|
||||||
|
auto success = comm_pt.get_optional<bool> ("success");
|
||||||
|
if (!(success && *success))
|
||||||
|
{
|
||||||
|
auto errormsg = comm_pt.get_optional<std::string> ("errormsg");
|
||||||
|
if (errormsg && !errormsg->empty())
|
||||||
|
std::cout << _("Finance::Quote reported a failure for symbol ") <<
|
||||||
|
comm << ": " << *errormsg << std::endl;
|
||||||
|
else
|
||||||
|
std::cout << _("Finance::Quote failed silently to retrieve a quote for symbol ") <<
|
||||||
|
comm << std::endl;
|
||||||
|
return empty_tree;
|
||||||
|
}
|
||||||
|
return comm_pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
show_quotes(const bpt::ptree& pt, const StrVec& commodities, bool verbose)
|
||||||
|
{
|
||||||
|
for (auto comm : commodities)
|
||||||
|
{
|
||||||
|
auto comm_pt{get_commodity_data(pt, comm)};
|
||||||
|
|
||||||
|
if (comm_pt == empty_tree)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
std::cout << comm << ":\n";
|
||||||
|
show_verbose_quote(comm_pt);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
show_gnucash_quote(comm_pt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
show_currency_quotes(const bpt::ptree& pt, const StrVec& commodities, bool verbose)
|
||||||
|
{
|
||||||
|
auto to_cur{commodities.front()};
|
||||||
|
for (auto comm : commodities)
|
||||||
|
{
|
||||||
|
if (comm == to_cur)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto comm_pt{get_commodity_data(pt, comm)};
|
||||||
|
|
||||||
|
if (comm_pt == empty_tree)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
std::cout << comm << ":\n";
|
||||||
|
show_verbose_quote(comm_pt);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << "1 " << comm << " = " <<
|
||||||
|
comm_pt.get<char>("last", "Not Found") << " " << to_cur << "\n";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
/********************************************************************
|
/********************************************************************
|
||||||
* gnc_quotes_get_quotable_commodities
|
* gnc_quotes_get_quotable_commodities
|
||||||
* list commodities in a given namespace that get price quotes
|
* list commodities in a given namespace that get price quotes
|
||||||
@ -805,6 +1008,12 @@ void GncQuotes::fetch (gnc_commodity *comm)
|
|||||||
m_impl->fetch (comm);
|
m_impl->fetch (comm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GncQuotes::report (const char* source, const StrVec& commodities,
|
||||||
|
bool verbose)
|
||||||
|
{
|
||||||
|
m_impl->report(source, commodities, verbose);
|
||||||
|
}
|
||||||
|
|
||||||
const std::string& GncQuotes::version() noexcept
|
const std::string& GncQuotes::version() noexcept
|
||||||
{
|
{
|
||||||
return m_impl->version ();
|
return m_impl->version ();
|
||||||
|
@ -91,7 +91,14 @@ public:
|
|||||||
* @note Commodity must have a quote source set or the call will silently fail.
|
* @note Commodity must have a quote source set or the call will silently fail.
|
||||||
*/
|
*/
|
||||||
void fetch (gnc_commodity *comm);
|
void fetch (gnc_commodity *comm);
|
||||||
|
/** Report quote results from Finance::Quote to std::cout.
|
||||||
|
*
|
||||||
|
* @param source A valid quote source
|
||||||
|
* @param commodities A std::vector of symbols to request quotes for.
|
||||||
|
* @note If requesting currency rates the first symbol is the to-currency and the rest are from-currencies. For example, {"USD", "EUR", "CAD"} will print the price of 1 Euro and 1 Canadian Dollar in US Dollars.
|
||||||
|
* @param verbose Ignored for currency queries. If false it will print the six fields GnuCash uses regardless of whether a value was returned; if true it will print all of the fields for which Finanace::Quote returned values.
|
||||||
|
*/
|
||||||
|
void report (const char* source, const StrVec& commodities, bool verbose = false);
|
||||||
/** Get the installed Finance::Quote version
|
/** Get the installed Finance::Quote version
|
||||||
*
|
*
|
||||||
* @return the Finance::Quote version string
|
* @return the Finance::Quote version string
|
||||||
|
@ -142,10 +142,10 @@ TEST_F(GncQuotesTest, online_wiggle)
|
|||||||
quotes.fetch(m_book);
|
quotes.fetch(m_book);
|
||||||
auto pricedb{gnc_pricedb_get_db(m_book)};
|
auto pricedb{gnc_pricedb_get_db(m_book)};
|
||||||
auto failures{quotes.failures()};
|
auto failures{quotes.failures()};
|
||||||
ASSERT_EQ(2u, failures.size());
|
ASSERT_EQ(1u, failures.size());
|
||||||
EXPECT_EQ(GncQuoteError::QUOTE_FAILED, std::get<2>(failures[0]));
|
EXPECT_EQ(GncQuoteError::QUOTE_FAILED, std::get<2>(failures[0]));
|
||||||
EXPECT_EQ(GncQuoteError::QUOTE_FAILED, std::get<2>(failures[1]));
|
// EXPECT_EQ(GncQuoteError::QUOTE_FAILED, std::get<2>(failures[1]));
|
||||||
EXPECT_EQ(2u, gnc_pricedb_get_num_prices(pricedb));
|
EXPECT_EQ(3u, gnc_pricedb_get_num_prices(pricedb));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -164,6 +164,29 @@ TEST_F(GncQuotesTest, offline_wiggle)
|
|||||||
EXPECT_EQ(3u, gnc_pricedb_get_num_prices(pricedb));
|
EXPECT_EQ(3u, gnc_pricedb_get_num_prices(pricedb));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(GncQuotesTest, offline_report)
|
||||||
|
{
|
||||||
|
StrVec quote_vec{
|
||||||
|
"{\"AAPL\":{\"eps\":6.05,\"success\":1,\"year_range\":\" 129.04 - 182.94\",\"currency\":\"USD\",\"exchange\":\"Sourced from Yahoo Finance (as JSON)\",\"volume\":73539475,\"close\":157.22,\"high\":158.39,\"open\":156.64,\"div_yield\":0.5660857,\"last\":157.96,\"isodate\":\"2022-09-01\",\"method\":\"yahoo_json\",\"name\":\"AAPL (Apple Inc.)\",\"pe\":26.10909,\"low\":154.67,\"type\":\"EQUITY\",\"symbol\":\"AAPL\",\"date\":\"09/01/2022\"},\"HPE\":{\"symbol\":\"HPE\",\"date\":\"09/01/2022\",\"low\":13.13,\"type\":\"EQUITY\",\"method\":\"yahoo_json\",\"name\":\"HPE (Hewlett Packard Enterprise Comp)\",\"isodate\":\"2022-09-01\",\"pe\":4.7921147,\"last\":13.37,\"high\":13.535,\"close\":13.6,\"open\":13.5,\"div_yield\":3.5294116,\"volume\":16370483,\"exchange\":\"Sourced from Yahoo Finance (as JSON)\",\"currency\":\"USD\",\"year_range\":\" 12.4 - 17.76\",\"eps\":2.79,\"success\":1},\"FKCM\":{\"success\":0,\"symbol\":\"FKCM\",\"errormsg\":\"Error retrieving quote for FKCM - no listing for this name found. Please check symbol and the two letter extension (if any)\"}}"
|
||||||
|
};
|
||||||
|
StrVec commodities{"AAPL", "HPE", "FKCM"};
|
||||||
|
StrVec err_vec;
|
||||||
|
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
|
||||||
|
quotes.report("yahoo_json", commodities, false);
|
||||||
|
quotes.report("yahoo_json", commodities, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GncQuotesTest, offline_currency_report)
|
||||||
|
{
|
||||||
|
StrVec quote_vec{
|
||||||
|
"{\"EUR\":{\"symbol\":\"EUR\",\"currency\":\"USD\",\"success\":\"1\",\"inverted\":0,\"last\":1.0004}}"};
|
||||||
|
StrVec commodities{"USD", "EUR"};
|
||||||
|
StrVec err_vec;
|
||||||
|
GncQuotesImpl quotes(m_book, std::make_unique<GncMockQuoteSource>(std::move(quote_vec), std::move(err_vec)));
|
||||||
|
quotes.report("currency", commodities, false);
|
||||||
|
quotes.report("currency", commodities, true);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(GncQuotesTest, comvec_fetch)
|
TEST_F(GncQuotesTest, comvec_fetch)
|
||||||
{
|
{
|
||||||
StrVec quote_vec{
|
StrVec quote_vec{
|
||||||
|
Loading…
Reference in New Issue
Block a user