Restored --download, although not done yet

The problem at this point is that it's recording prices in the price
database multiple times; it should only need to download a price for
each commodity once per day.
This commit is contained in:
John Wiegley 2009-06-24 02:44:07 +01:00
parent 72a2eaa38e
commit 440124eacc
9 changed files with 168 additions and 84 deletions

View file

@ -1,8 +1,9 @@
#!/usr/bin/perl
#!/usr/bin/env perl
$timeout = 60;
use Finance::Quote;
use POSIX qw(strftime localtime time);
$q = Finance::Quote->new;
$q->timeout($timeout);
@ -10,6 +11,8 @@ $q->require_labels(qw/price/);
%quotes = $q->fetch("nasdaq", $ARGV[0]);
if ($quotes{$ARGV[0], "price"}) {
print strftime('%Y/%m/%d %H:%M:%S', localtime(time()));
print " ", $ARGV[0], " ";
print "\$", $quotes{$ARGV[0], "price"}, "\n";
} else {
exit 1;

View file

@ -36,6 +36,10 @@
namespace ledger {
optional<path> commodity_t::price_db;
long commodity_t::download_leeway = 86400;
bool commodity_t::download_quotes;
void commodity_t::base_t::history_t::add_price(commodity_t& source,
const datetime_t& date,
const amount_t& price,
@ -105,10 +109,108 @@ bool commodity_t::base_t::varied_history_t::remove_price(const datetime_t& date
return false;
}
optional<price_point_t> commodity_t::parse_commodity_price(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 {
symbol_and_price = time_field_ptr;
datetime = parse_datetime(date_field);
}
string symbol;
parse_symbol(symbol_and_price, symbol);
price_point_t point;
point.when = datetime;
point.price.parse(symbol_and_price);
VERIFY(point.price.valid());
if (commodity_t * commodity =
amount_t::current_pool->find_or_create(symbol)) {
commodity->add_price(point.when, point.price, true);
commodity->add_flags(COMMODITY_KNOWN);
return point;
}
return none;
}
optional<price_point_t>
commodity_t::download_quote(const optional<commodity_t&>& commodity) const
{
DEBUG("commodity.download", "downloading quote for symbol " << symbol());
#if defined(DEBUG_ON)
if (commodity)
DEBUG("commodity.download",
" in terms of commodity " << commodity->symbol());
#endif
char buf[256];
buf[0] = '\0';
string getquote_cmd("getquote \"");
getquote_cmd += symbol();
getquote_cmd += "\" \"";
if (commodity)
getquote_cmd += commodity->symbol();
getquote_cmd += "\"";
DEBUG("commodity.download", "invoking command: " << getquote_cmd);
bool success = true;
if (FILE * fp = popen(getquote_cmd.c_str(), "r")) {
if (std::feof(fp) || ! std::fgets(buf, 255, fp))
success = false;
if (pclose(fp) != 0)
success = false;
} else {
success = false;
}
if (success && buf[0]) {
char * p = std::strchr(buf, '\n');
if (p) *p = '\0';
DEBUG("commodity.download", "downloaded quote: " << buf);
optional<price_point_t> point = parse_commodity_price(buf);
if (point) {
if (price_db) {
#if defined(__GNUG__) && __GNUG__ < 3
ofstream database(*price_db, ios::out | ios::app);
#else
ofstream database(*price_db, std::ios_base::out | std::ios_base::app);
#endif
database << "P " << format_datetime(point->when, string("%Y/%m/%d %H:%M:%S"))
<< " " << symbol() << " " << point->price << std::endl;
}
return point;
}
} else {
throw_(std::runtime_error,
_("Failed to download price for '%1' (command: \"getquote %2\")")
<< symbol() << symbol());
}
return none;
}
optional<price_point_t>
commodity_t::base_t::history_t::
find_price(const optional<datetime_t>& moment,
const optional<datetime_t>& oldest
find_price(const optional<datetime_t>& moment,
const optional<datetime_t>& oldest
#if defined(DEBUG_ON)
, const int indent
#endif
@ -185,16 +287,6 @@ optional<price_point_t>
}
}
#if 0
if (! has_flags(COMMODITY_NOMARKET) && parent().get_quote) {
if (optional<amount_t> quote = parent().get_quote
(*this, age, moment,
(hist && hist->prices.size() > 0 ?
(*hist->prices.rbegin()).first : optional<datetime_t>())))
return *quote;
}
#endif
if (! found) {
#if defined(DEBUG_ON)
DEBUG_INDENT("commodity.prices.find", indent);
@ -351,7 +443,29 @@ optional<price_point_t>
DEBUG_INDENT("commodity.prices.find", indent);
DEBUG("commodity.prices.find",
" found price " << best.price << " from " << best.when);
DEBUG("commodity.download",
"found price " << best.price << " from " << best.when);
if (moment)
DEBUG("commodity.download", "moment = " << *moment);
DEBUG("commodity.download", "leeway = " << download_leeway);
if (moment)
DEBUG("commodity.download",
"slip.moment = " << (*moment - best.when).total_seconds());
else
DEBUG("commodity.download",
"slip.now = " << (CURRENT_TIME() - best.when).total_seconds());
#endif
if (download_quotes &&
! source.has_flags(COMMODITY_NOMARKET) &&
((! moment &&
(CURRENT_TIME() - best.when).total_seconds() > download_leeway) ||
(moment &&
(*moment - best.when).total_seconds() > download_leeway))) {
DEBUG("commodity.download",
"attempting to download a more current quote...");
if (optional<price_point_t> quote = source.download_quote(commodity))
return quote;
}
return best;
}
return none;
@ -368,7 +482,8 @@ optional<commodity_t::base_t::history_t&>
#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 -x)"));
_("Cannot determine price history: "
"prices known for multiple commodities (use -x)"));
#endif
comm = (*histories.begin()).first;
} else {

View file

@ -86,7 +86,6 @@ public:
struct history_t
{
history_map prices;
ptime last_lookup;
void add_price(commodity_t& source,
const datetime_t& date,
@ -332,6 +331,15 @@ public:
// Methods related to parsing, reading, writing, etc., the commodity
// itself.
static optional<path> price_db;
static long download_leeway;
static bool download_quotes;
static optional<price_point_t> parse_commodity_price(char * line);
optional<price_point_t>
download_quote(const optional<commodity_t&>& commodity = none) const;
static void parse_symbol(std::istream& in, string& symbol);
static void parse_symbol(char *& p, string& symbol);
static string parse_symbol(std::istream& in) {

View file

@ -415,10 +415,21 @@ void global_scope_t::normalize_report_options(const string& verb)
report_t& rep(report());
// jww (2009-02-09): These global are a hack, but hard to avoid.
// jww (2009-02-09): These globals are a hack, but hard to avoid.
item_t::use_effective_date = rep.HANDLED(effective);
rep.session.commodity_pool->keep_base = rep.HANDLED(base);
commodity_t::download_quotes = rep.session.HANDLED(download);
if (rep.session.HANDLED(price_exp_))
commodity_t::download_leeway =
rep.session.HANDLER(price_exp_).value.as_long();
if (rep.session.HANDLED(price_db_))
commodity_t::price_db = rep.session.HANDLER(price_db_).str();
else
commodity_t::price_db = none;
if (rep.HANDLED(date_format_)) {
output_datetime_format = rep.HANDLER(date_format_).str() + " %H:%M:%S";
output_date_format = rep.HANDLER(date_format_).str();

View file

@ -474,9 +474,6 @@ option_t<report_t> * report_t::lookup_option(const char * p)
case 'Y':
OPT_CH(yearly);
break;
case 'Z':
OPT_CH(price_exp_);
break;
case 'a':
OPT(abbrev_len_);
else OPT(account_);
@ -557,7 +554,6 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(lots);
else OPT(lots_actual);
else OPT_ALT(tail_, last_);
else OPT_ALT(price_exp_, leeway_);
break;
case 'm':
OPT(market);
@ -582,7 +578,6 @@ option_t<report_t> * report_t::lookup_option(const char * p)
else OPT(plot_amount_format_);
else OPT(plot_total_format_);
else OPT(price);
else OPT(price_exp_);
else OPT(prices_format_);
else OPT(pricesdb_format_);
else OPT(print_format_);

View file

@ -255,7 +255,6 @@ public:
HANDLER(plot_amount_format_).report(out);
HANDLER(plot_total_format_).report(out);
HANDLER(price).report(out);
HANDLER(price_exp_).report(out);
HANDLER(prices_format_).report(out);
HANDLER(pricesdb_format_).report(out);
HANDLER(print_format_).report(out);
@ -619,8 +618,6 @@ public:
parent->HANDLER(amount_).set_expr(string("--price"), "price");
});
OPTION(report_t, price_exp_); // -Z
OPTION__(report_t, prices_format_, CTOR(report_t, prices_format_) {
on(none,
"%-.9(date) %-8(account) %(justify(scrub(display_amount), 12, "

View file

@ -219,6 +219,12 @@ void session_t::clean_accounts()
option_t<session_t> * session_t::lookup_option(const char * p)
{
switch (*p) {
case 'Q':
OPT_CH(download); // -Q
break;
case 'Z':
OPT_CH(price_exp_);
break;
case 'a':
OPT_(account_); // -a
break;
@ -232,17 +238,15 @@ option_t<session_t> * session_t::lookup_option(const char * p)
OPT(input_date_format_);
break;
case 'l':
OPT(leeway_);
OPT_ALT(price_exp_, leeway_);
break;
case 'p':
OPT(price_db_);
else OPT(price_exp_);
break;
case 's':
OPT(strict);
break;
case 'Q':
OPT_CH(download); // -Q
break;
}
return NULL;
}

View file

@ -105,10 +105,10 @@ public:
{
HANDLER(account_).report(out);
HANDLER(download).report(out);
HANDLER(leeway_).report(out);
HANDLER(file_).report(out);
HANDLER(input_date_format_).report(out);
HANDLER(price_db_).report(out);
HANDLER(price_exp_).report(out);
HANDLER(strict).report(out);
}
@ -124,8 +124,8 @@ public:
OPTION(session_t, download); // -Q
OPTION__
(session_t, leeway_,
CTOR(session_t, leeway_) { value = 24L * 3600L; }
(session_t, price_exp_, // -Z
CTOR(session_t, price_exp_) { value = 24L * 3600L; }
DO_(args) {
value = args[1].to_long() * 60L;
});

View file

@ -455,67 +455,18 @@ void instance_t::price_conversion_directive(char * line)
}
}
namespace {
void parse_symbol(char *& p, string& symbol)
{
if (*p == '"') {
char * q = std::strchr(p + 1, '"');
if (! q)
throw parse_error(_("Quoted commodity symbol lacks closing quote"));
symbol = string(p + 1, 0, q - p - 1);
p = q + 2;
} else {
char * q = next_element(p);
symbol = p;
if (q)
p = q;
else
p += symbol.length();
}
if (symbol.empty())
throw parse_error(_("Failed to parse commodity"));
}
}
void instance_t::price_xact_directive(char * line)
{
char * date_field_ptr = skip_ws(line + 1);
char * time_field_ptr = next_element(date_field_ptr);
if (! time_field_ptr) return;
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;
datetime = parse_datetime(date_field + " " + time_field_ptr,
current_year);
} else {
symbol_and_price = time_field_ptr;
datetime = parse_datetime(date_field, current_year);
}
string symbol;
parse_symbol(symbol_and_price, symbol);
amount_t price(symbol_and_price);
VERIFY(price.valid());
if (commodity_t * commodity =
amount_t::current_pool->find_or_create(symbol)) {
commodity->add_price(datetime, price, true);
commodity->add_flags(COMMODITY_KNOWN);
} else {
assert(false);
}
optional<price_point_t> point =
commodity_t::parse_commodity_price(skip_ws(line + 1));
assert(point);
}
void instance_t::nomarket_directive(char * line)
{
char * p = skip_ws(line + 1);
string symbol;
parse_symbol(p, symbol);
commodity_t::parse_symbol(p, symbol);
if (commodity_t * commodity =
amount_t::current_pool->find_or_create(symbol))