// Author: Po-Shen Loh
// Copyright (c) 2004-2006
//
// version 2.0 Feb 13, 2005
// version 3.0 Apr 16, 2005 adapt for missing data by using the last good data
// version 4.0 Aug 31, 2005 create a second plot with all returns timeseries anchored
// at the final timepoint instead of the initial timepoint.
// Nov 5, 2005 also correct the error of adding value for fees instead of deducting.
// version 5.0 Feb 23, 2006 rename final column to "COMMENTS"
// | also correct accounting errors during partial sales
// | and undo double-addition of mutual fund dividends.
// | and incorrect starting from 2nd day instead of 1st
// | and removed hardcoding of initial index values
// | and corrected for earlier data in .csv files than first trade
// | and made Transactions automatically sort
// V and made quotes download automatically, instead of req'ing a manual script
// Mar 24, 2006 and collapsed gnuplot-data.txt and returns.txt into one file
#include <unistd.h>
#include <sys/wait.h>
#include <time.h>
#include <math.h>
#include <ctype.h>
#include <iostream>
#include <fstream>
#include <set>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
typedef long double LD;
// ==================================== output filenames ====================================
const char transactions_filename[] = "Transactions.txt";
const char returns_filename[] = "returns.txt";
const char gnuplot_script_filename[] = "plot.scr";
const char png_chart_filename[] = "performance.png";
// ==================================== yahoo index/symbol codes ====================================
string symbol_code(string index) {
if (index == "$DOW") return "^DJI";
if (index == "$NASDAQ") return "^IXIC";
if (index == "$SP500") return "^GSPC";
if (index.find("/") != index.npos) {
string ret;
for (size_t i = 0; i < index.size(); ++i)
if (index[i] != '/')
ret += index[i];
else
ret += '-';
return ret;
}
return index;
}
// ==================================== utility functions ====================================
const string reverse_month[13] = {"Zero", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
map<string, int> lookup_month;
struct ltstr_symbol {
bool operator()(const string s1, const string s2) const {
if (s1.find('$') == s1.npos && s2.find('$') != s2.npos) return true;
if (s1.find('$') != s1.npos && s2.find('$') == s2.npos) return false;
return s1 < s2;
}
};
string tolower(string in) {
string ret;
for (size_t k = 0; k < in.size(); ++k)
ret += tolower(in[k]);
return ret;
}
vector<string> split(string food, string separators=" \t") {
bool active = false; int begin = -1; vector<string> ret;
for (size_t i = 0; i < food.size(); ++i)
if (active) {
if (strchr(separators.c_str(), food[i]) != NULL)
{ ret.push_back(food.substr(begin, i-begin)); active = false; }
}
else if (strchr(separators.c_str(), food[i]) == NULL)
{ begin = i; active = true; }
if (active) ret.push_back(food.substr(begin));
return ret;
}
string toString(int d) { char ret[30]; sprintf(ret, "%d", d); return ret; }
string DtoString(double d) { char ret[30]; sprintf(ret, "%0.2lf", d); return ret; }
int toInt(string str) { int ret; sscanf(str.c_str(), "%d", &ret); return ret; }
double toDouble(string str) { double ret; sscanf(str.c_str(), "%lf", &ret); return ret; }
// ==================================== class DATE ====================================
class Date {
public:
int month, day, year;
void parse(string date) {
if (date.find('-') != date.npos) { // 10-Jun-04
vector<string> st = split(date, "-");
day = toInt(st[0]);
year = toInt(st[2]);
month = lookup_month[tolower(st[1]).substr(0, 3)];
}
else if (date.find('/') != date.npos) { // 06/10/04
sscanf(date.c_str(), "%d/%d/%d", &month, &day, &year);
}
else { // 10 Jun 2004
vector<string> st = split(date);
day = toInt(st[0]);
year = toInt(st[2]);
month = lookup_month[tolower(st[1]).substr(0, 3)];
}
if (year < 1900)
year += 2000;
}
string toString(void) const {
return ::toString(day) + " " + reverse_month[month] + " " + ::toString(year);
}
bool operator<(const class Date &b) const {
if (year < b.year) return true;
if (year > b.year) return false;
if (month < b.month) return true;
if (month > b.month) return false;
return day < b.day;
}
bool operator==(const class Date &b) const {
return year==b.year && month==b.month && day==b.day;
}
bool operator<=(const class Date &b) const {
return *this == b || *this < b;
}
bool operator>(const class Date &b) const {
return !(*this <= b);
}
bool operator>=(const class Date &b) const {
return !(*this < b);
}
};
Date make_date(int mo, int da, int yr) {
Date d;
d.month = mo;
d.day = da;
d.year = yr;
return d;
}
// ==================================== class STOCKDATA ====================================
class StockData {
public:
double open, last, chg, pctchg, high, low, vol, shares, totcost, gain, pctgain, totvalue;
string comments;
StockData(void) : open(0), last(0), chg(0), pctchg(0), high(0), low(0), vol(0), shares(0), totcost(0), gain(0), pctgain(0), totvalue(0) {
return;
}
StockData operator+(const StockData &b) const {
StockData ret;
ret.totcost = totcost + b.totcost;
ret.shares = shares + b.shares;
return ret;
}
const StockData& operator+=(const StockData &b) { *this = *this+b; return *this; }
};
// ==================================== class PORTFOLIO ====================================
class Portfolio {
public:
map<string, StockData, ltstr_symbol> stocks;
double gain, totcost, totgain, value;
long double pctgain;
double dividend, cap_adj;
Portfolio(void) : gain(0), totcost(0), totgain(0), value(0), pctgain(0), dividend(0), cap_adj(0) {
return;
}
};
// ==================================== class TRANSACTIONREC ====================================
class TransactionRec {
public:
Date date;
string op;
double shares;
string symbol;
double totcost;
bool operator<(const TransactionRec &b) const {
return date < b.date;
}
};
// ==================================== class RETURNSREC ====================================
class ReturnsRec {
public:
int day, month, year;
long double myfundd, dowd, nasdaqd, sp500d;
long double myfund0, dow0, nasdaq0, sp5000; // anchored at initial point
long double myfund1, dow1, nasdaq1, sp5001; // anchored at final point
};
// ==================================== global variables ====================================
double first_dow, first_nasdaq, first_sp500;
double total_dividend=0;
set<Date> trading_days;
Date first_trading_day;
Date actual_today;
map<Date, Portfolio> data;
map<string, StockData> last_good_data;
map<string, Date> first_date;
map<string, Date> last_date;
ofstream returns_file(returns_filename);
ofstream script(gnuplot_script_filename);
ofstream tex_file;
// ==================================== get today's date ====================================
void get_today_date(void) {
time_t tim = time(NULL);
struct tm *t = gmtime(&tim);
actual_today.month = t->tm_mon+1;
actual_today.day = t->tm_mday;
actual_today.year = 1900 + t->tm_year;
}
// ==================================== prepare month lookup ====================================
void prep_lookup(void) {
lookup_month["jan"] = 1;
lookup_month["feb"] = 2;
lookup_month["mar"] = 3;
lookup_month["apr"] = 4;
lookup_month["may"] = 5;
lookup_month["jun"] = 6;
lookup_month["jul"] = 7;
lookup_month["aug"] = 8;
lookup_month["sep"] = 9;
lookup_month["oct"] = 10;
lookup_month["nov"] = 11;
lookup_month["dec"] = 12;
}
// ==================================== download quotes ====================================
void dl_quotes(string symbol, Date first, Date last, const char *filename) {
char url[1000];
sprintf(url, "http://table.finance.yahoo.com/table.csv?s=%s&a=%02d&b=%02d&c=%02d&d=%02d&e=%02d&f=%d&g=d&ignore=.csv", symbol.c_str(), first.month-1, first.day, first.year, last.month-1, last.day, last.year);
pid_t pid;
int status=0;
switch (pid = fork()) {
case -1:
cerr << "ERR dl_quotes: fork failed" << endl;
exit(1);
break;
case 0: // child process calls exec system call
cerr << "filename: " << filename << endl;
cerr << "url: " << url << endl;
execl("/usr/bin/wget", "wget", "-q", "-O", filename, url, NULL);
cerr << "ERR dl_quotes: exec failed" << endl;
exit(1);
break;
default: // parent process uses wait system call to suspend until the child finishes
wait(&status);
if (status != 0) {
cerr << "ERR dl_quotes: wget failed" << endl;
exit(1);
}
cerr << "NOTE dl_quotes: " << symbol << " finished" << endl;
}
}
// ==================================== read portfolio ====================================
void init_portfolio(void) {
// read in the transactions
char line[1000];
vector<TransactionRec> vt;
ifstream trans(transactions_filename);
char c_dstr[100];
char c_symbol[100];
char c_op[100];
TransactionRec tr;
StockData stdata;
while (trans.getline(line, 999)) {
if (strlen(line) < 1) break;
sscanf(line, "%s %s %lf %s %*[$]%lf", c_dstr, c_op, &stdata.shares, c_symbol, &stdata.totcost);
tr.date.parse(c_dstr);
tr.op = c_op;
tr.shares = stdata.shares;
tr.symbol = c_symbol;
tr.totcost = stdata.totcost;
vt.push_back(tr);
}
stable_sort(vt.begin(), vt.end()); // sort!
if (vt.size() == 0) {
cerr << "ERR init_portfolio: no transactions in file: " << transactions_filename << endl;
exit(1);
}
// load the trading days
dl_quotes(symbol_code("$SP500"), vt[0].date, actual_today, ".quotes+>Dates.csv"); // S&P 500
ifstream dates(".quotes+>Dates.csv");
Portfolio blank;
Date date;
first_trading_day = actual_today;
Date last_trading_day = make_date(1, 1, 1);
blank.stocks["$DOW"];
blank.stocks["$NASDAQ"];
blank.stocks["$SP500"];
dates.getline(line, 999);
while (dates.getline(line, 999)) {
if (line[0] == '<') break;
if (strlen(line) < 1) break;
vector<string> st = split(line, ",");
date.parse(st[0]);
data[date] = blank;
trading_days.insert(date);
if (date > last_trading_day) last_trading_day = date;
if (date < first_trading_day) first_trading_day = date;
}
dates.close();
first_date["$DOW"] = first_date["$NASDAQ"] = first_date["$SP500"] = first_trading_day;
last_date["$DOW"] = last_date["$NASDAQ"] = last_date["$SP500"] = last_trading_day;
// now process the transactions
for (size_t z = 0; z < vt.size(); ++z) {
date = vt[z].date;
string op = vt[z].op;
stdata.shares = vt[z].shares;
string symbol= vt[z].symbol;
stdata.totcost = vt[z].totcost;
if (op == "Buy") {
if (first_date.count(symbol) == 0 || last_date[symbol] <= date) {
first_date[symbol] = *trading_days.lower_bound(date);
last_date[symbol] = last_trading_day;
}
for (map<Date, Portfolio>::iterator it = data.begin(); it != data.end(); ++it)
if (it->first >= date) {
it->second.stocks[symbol] += stdata;
}
}
else if (op == "Reinvest") {
bool first = true;
for (map<Date, Portfolio>::iterator it = data.begin(); it != data.end(); ++it)
if (it->first >= date) {
if (first) {
it->second.dividend -= stdata.totcost; // but punish by deducting from dividend
stdata.totcost = 0; // simple adding of shares
first = false;
}
it->second.stocks[symbol] += stdata;
}
}
else if (op == "Dividend") {
for (map<Date, Portfolio>::iterator it = data.begin(); it != data.end(); ++it)
if (it->first >= date) {
it->second.dividend += stdata.totcost;
break;
}
}
else if (op == "Fee") {
for (map<Date, Portfolio>::iterator it = data.begin(); it != data.end(); ++it)
if (it->first >= date) {
it->second.dividend -= stdata.totcost;
break;
}
}
else { // sell
bool kill = false;
bool first = true;
double new_totcost;
for (map<Date, Portfolio>::iterator it = data.begin(); it != data.end(); ++it)
if (it->first >= date) {
if (first) {
if (it->second.stocks[symbol].shares <= stdata.shares + 1e-5) { // tolerance
kill = true;
last_date[symbol] = it->first;
}
else {
double propleft = (it->second.stocks[symbol].shares - stdata.shares) / it->second.stocks[symbol].shares;
new_totcost = it->second.stocks[symbol].totcost * propleft;
it->second.cap_adj += stdata.totcost - (1-propleft) * it->second.stocks[symbol].totcost;
}
first = false;
}
if (kill) {
if (it->first == date)
it->second.stocks[symbol].last = fabs(stdata.totcost/stdata.shares); // adjust price to reflect sale of all shares
else
it->second.stocks.erase(symbol);
}
else {
it->second.stocks[symbol].shares -= stdata.shares;
it->second.stocks[symbol].totcost = new_totcost;
}
}
}
}
}
// ==================================== canonicalize filename for quotes ====================================
string quotes_filename(string symbol) {
if (symbol.size() == 0) {
cerr << "ERR quotes_filename: empty symbol" << endl;
exit(1);
}
if (symbol[0] == '$')
return ".quotes+>index-" + symbol.substr(1) + ".csv";
else if (symbol.find('/') != symbol.npos) {
string noslash;
for (size_t k = 0; k < symbol.size(); ++k)
if (symbol[k] != '/')
noslash += symbol[k];
return ".quotes+>" + noslash + ".csv";
}
else
return ".quotes+>" + symbol + ".csv";
}
// ==================================== prepare .csv timeseries ====================================
void prep_data(void) {
for (map<string, Date>::iterator it = first_date.begin(); it != first_date.end(); ++it) {
string symbol = it->first;
string filename = quotes_filename(symbol);
Date fd = first_date[symbol];
Date ld = last_date[symbol];
cerr << "NOTE prep_data: " << symbol << " : " << fd.toString() << " --> " << ld.toString() << endl;
// try to verify quotes file
ifstream quotes(filename.c_str());
bool incomplete = true;
if (quotes) {
bool got_first=false, got_last=false;
Date date;
char line[1000];
char dstr[1000];
quotes.getline(line, 999);
while (quotes.getline(line, 999)) {
if (strlen(line) < 1) continue;
if (line[0] == '<') continue; // comments in CSV file
sscanf(line, "%[^,]", dstr);
date.parse(dstr);
if (date == fd) got_first = true;
if (date == ld) got_last = true;
}
incomplete = !got_first || !got_last;
quotes.close();
}
if (incomplete) {
cerr << "NOTE prep_data: " << symbol << " data INCOMPLETE; updating..." << endl;
dl_quotes(symbol_code(symbol), fd, ld, filename.c_str());
}
else
cerr << "NOTE prep_data: " << symbol << " data complete" << endl;
}
}
// ==================================== load .csv timeseries ====================================
void load_prices(void) {
char line[1000];
Date date;
// now all transactions are loaded; load all prices
for (map<Date, Portfolio>::iterator it = data.begin(); it != data.end(); ++it) {
for (map<string, StockData, ltstr_symbol>::iterator it2 = it->second.stocks.begin(); it2 != it->second.stocks.end(); ++it2) {
if (it2->second.last < 1) {
// load prices for it2->first, the symbol of the stock
ifstream history(quotes_filename(it2->first).c_str());
if (!history) {
cerr << "ERR load_prices: missing file for " << it2->first << endl;
exit(1);
}
history.getline(line, 999);
while (history.getline(line, 999)) {
if (strlen(line) < 1) continue;
if (line[0] == '<') continue; // comments in CSV file
vector<string> st = split(line, ",");
date.parse(st[0]);
if (data.count(date) > 0 && data[date].stocks.count(it2->first) > 0) {
data[date].stocks[it2->first].open = toDouble(st[1]);
data[date].stocks[it2->first].high = toDouble(st[2]);
data[date].stocks[it2->first].low = toDouble(st[3]);
if (data[date].stocks[it2->first].last < 1)
data[date].stocks[it2->first].last = toDouble(st[4]);
data[date].stocks[it2->first].vol = toDouble(st[5]);
data[date].stocks[it2->first].totvalue = data[date].stocks[it2->first].last * data[date].stocks[it2->first].shares;
data[date].stocks[it2->first].gain = data[date].stocks[it2->first].totvalue - data[date].stocks[it2->first].totcost;
if (data[date].stocks[it2->first].totcost > 1)
data[date].stocks[it2->first].pctgain = 100 * data[date].stocks[it2->first].gain / data[date].stocks[it2->first].totcost;
}
}
}
}
}
}
// ==================================== calculate returns ====================================
void compute_changes(void) {
map<Date, Portfolio>::iterator prev, today;
bool first = true;
today = data.begin();
while (today != data.end()) {
today->second.gain = today->second.dividend + today->second.cap_adj;
total_dividend += today->second.dividend;
for (map<string, StockData, ltstr_symbol>::iterator it = today->second.stocks.begin(); it != today->second.stocks.end(); ++it) {
string sym = it->first;
if (!first && prev->second.stocks.count(sym)) {
if (it->second.last < 0.01) // missing data point
it->second = last_good_data[it->first];
it->second.chg = it->second.last - prev->second.stocks[sym].last;
it->second.pctchg = 100 * it->second.chg / prev->second.stocks[sym].last;
today->second.gain += (it->second.totvalue - it->second.totcost) - (prev->second.stocks[sym].totvalue - prev->second.stocks[sym].totcost);
}
else { // new acquisition
if (it->second.shares < 1e-5) {
// if index
double compare = it->second.last;
if (sym == "$DOW") {
first_dow = today->second.stocks["$DOW"].open;
compare = first_dow;
}
else if (sym == "$NASDAQ") {
first_nasdaq = today->second.stocks["$NASDAQ"].open;
compare = first_nasdaq;
}
else if (sym == "$SP500") {
first_sp500 = today->second.stocks["$SP500"].open;
compare = first_sp500;
}
it->second.chg = it->second.last - compare;
it->second.pctchg = 100 * it->second.chg / compare;
}
else {
it->second.chg = (it->second.totvalue - it->second.totcost) / it->second.shares;
it->second.pctchg = 100 * it->second.chg / (it->second.totcost / it->second.shares);
}
today->second.gain += it->second.totvalue - it->second.totcost;
}
last_good_data[it->first] = it->second;
today->second.totcost += it->second.totcost;
today->second.value += it->second.totvalue;
}
if (first) {
today->second.pctgain = LD(100) * today->second.gain / today->second.totcost;
today->second.totgain = today->second.gain;
first = false;
}
else {
today->second.pctgain = LD(100) * LD(today->second.gain) / LD(prev->second.value);
today->second.totgain = today->second.value - today->second.totcost;
}
prev = today;
++today;
}
}
// ==================================== helper output functions ====================================
void dump(string st, string la, string ch, string pc, string hi, string lo, string vo, string nu, string to, string ga, string pg, string mo) {
char buf[1000];
sprintf(buf, "%-7s%8s %8s %7s %9s %9s %8s %8s %11s %11s %7s %s", st.c_str(), la.c_str(), ch.c_str(), pc.c_str(), hi.c_str(), lo.c_str(), vo.c_str(), nu.c_str(), to.c_str(), ga.c_str(), pg.c_str(), mo.c_str());
tex_file << buf << endl;
}
void dump(string st, double la, double ch, double pc, double hi, double lo, int vo, double nu, double to, double ga, double pg, string mo) {
char buf[1000];
if (fabsl(nu - int(nu+0.5)) < 1e-5)
sprintf(buf, "%-7s%8.2lf %8.2lf %+7.2lf %9.2lf %9.2lf %8d %8d %11.2lf %11.2lf %+7.1lf %s", st.c_str(), la, ch, pc, hi, lo, vo, int(nu+0.5), to, ga, pg, mo.c_str());
else
sprintf(buf, "%-7s%8.2lf %8.2lf %+7.2lf %9.2lf %9.2lf %8d %8.3lf %11.2lf %11.2lf %+7.1lf %s", st.c_str(), la, ch, pc, hi, lo, vo, nu, to, ga, pg, mo.c_str());
tex_file << buf << endl;
}
void dailytotals(double dailychg, double pctchg, double grandtotcost, double grandtotgain) {
char buf[1000];
sprintf(buf, "%-6s %8s %+8.2lf %+7.2lf %9s %9s %8s %8s %11.2lf %11.2lf %+7.1lf =%.2lf", "", "", dailychg, pctchg, "", "", "", "", grandtotcost, grandtotgain, grandtotgain/grandtotcost*100, grandtotcost+grandtotgain);
tex_file << buf << endl;
}
// ==================================== write header for returns.txt ====================================
void init_returns_file(void) {
returns_file << "# Date | Portfolio NASDAQ S&P 500 Dow Jones | Portfolio NASDAQ S&P 500 Dow Jones | Portfolio NASDAQ S&P 500 Dow Jones" << endl;
returns_file << "#----------+-----------------------------------------------+-----------------------------------------------+-------------------------------------------------" << endl;
returns_file << "# | daily returns | cumulative returns | tail-anchored cumulative returns" << endl;
returns_file << "#----------+-----------------------------------------------+-----------------------------------------------+-------------------------------------------------" << endl;
}
// ==================================== write gnuplot script ====================================
void init_gnuplot(void) {
char buf[1000];
script << "set key left top" << endl;
script << "set xdata time" << endl;
script << "set timefmt \"%m/%d/%Y\"" << endl;
script << "set format x \"%b %y\"" << endl;
sprintf(buf, "set xrange [\"%02d/%02d/%d\":*]", first_trading_day.month, first_trading_day.day, first_trading_day.year);
script << buf << endl;
script << "set ylabel \"% gain\"" << endl;
script << endl;
script << "set term png transparent" << endl;
script << "set output \"" << png_chart_filename << "\"" << endl;
script << "set pointsize 0.5" << endl;
script << "plot '" << returns_filename << "' using 1:6 title \"Portfolio\", '"
<< returns_filename << "' using 1:7 title \"NASDAQ\", '"
<< returns_filename << "' using 1:8 title \"S&P 500\", '"
<< returns_filename << "' using 1:9 title \"Dow Jones\"" << endl;
script << endl;
script << "set pointsize 1" << endl;
script << "set term x11 1" << endl;
script << "set output" << endl;
script << "plot '" << returns_filename << "' using 1:6 title \"Portfolio\", '"
<< returns_filename << "' using 1:7 title \"NASDAQ\", '"
<< returns_filename << "' using 1:8 title \"S&P 500\", '"
<< returns_filename << "' using 1:9 title \"Dow Jones\"" << endl;
script << "set term x11 2" << endl;
script << "set ylabel \"% discount\"" << endl;
script << "plot '" << returns_filename << "' using 1:10 title \"Portfolio\" with lines, '"
<< returns_filename << "' using 1:11 title \"NASDAQ\" with lines, '"
<< returns_filename << "' using 1:12 title \"S&P 500\" with lines, '"
<< returns_filename << "' using 1:13 title \"Dow Jones\" with lines" << endl;
}
// ==================================== write LaTeX file ====================================
void print_data(void) {
tex_file << "\\documentclass{article}" << endl;
tex_file << "\\usepackage{alltt}" << endl;
tex_file << "\\setlength{\\oddsidemargin}{0.0in}" << endl;
tex_file << "\\setlength{\\topmargin}{-0.5in}" << endl;
tex_file << "\\setlength{\\headheight}{0.0in}" << endl;
tex_file << "\\setlength{\\headsep}{0.0in}" << endl;
tex_file << "\\setlength{\\textwidth}{6.5in}" << endl;
tex_file << "\\setlength{\\textheight}{10in}" << endl;
tex_file << "\\interlinepenalty 10000" << endl;
tex_file << "\\newcommand{\\markdate}[1]{\\subsubsection*{\\small #1}}" << endl;
tex_file << "\\pagestyle{empty}" << endl;
tex_file << "\\begin{document}" << endl;
tex_file << endl;
tex_file << endl;
tex_file << "{\\tiny" << endl;
tex_file << endl;
long double Tmyfund=1, Tdow=1, Tnasdaq=1, Tsp500=1;
double prev_dow = first_dow;
double prev_nasdaq = first_nasdaq;
double prev_sp500 = first_sp500;
char returnsbuf[1000];
vector<ReturnsRec> returns;
for (map<Date, Portfolio>::iterator it = data.begin(); it != data.end(); ++it) {
tex_file << endl;
tex_file << "\\markdate{" << it->first.toString() << "}" << endl;
tex_file << endl;
tex_file << "\\begin{alltt}" << endl;
dump("STOCK", "LAST", "CHG", "%CHG", "HIGH", "LOW", "VOL", "SHARES", "TOTCOST", "GAIN", "%GAIN", "COMMENTS");
bool indices = false;
for (map<string, StockData, ltstr_symbol>::iterator it2 = it->second.stocks.begin(); it2 != it->second.stocks.end(); ++it2) {
StockData st = it2->second;
if (!indices && it2->first.find('$') != it2->first.npos) {
indices = true;
if (it->second.dividend == 0)
dump("-", "", "", "", "", "", "", "", "", "", "", "");
else
dump("-", "", "", "", "", "", "", "", "", DtoString(it->second.dividend), "", "(dividend)");
}
dump(it2->first, st.last, st.chg, st.pctchg, st.high, st.low, int(st.vol/100), st.shares, st.totcost, st.gain, st.pctgain, st.comments);
}
dailytotals(it->second.gain, it->second.pctgain, it->second.totcost, it->second.totgain);
tex_file << "\\end{alltt}" << endl;
tex_file << endl;
// compute returns
ReturnsRec rr;
rr.myfundd = it->second.pctgain;
rr.dowd = (LD(it->second.stocks["$DOW"].last) - prev_dow)/prev_dow * 100;
rr.nasdaqd = (LD(it->second.stocks["$NASDAQ"].last) - prev_nasdaq)/prev_nasdaq * 100;
rr.sp500d = (LD(it->second.stocks["$SP500"].last) - prev_sp500)/prev_sp500 * 100;
Tmyfund *= 1 + it->second.pctgain/100;
Tdow = LD(it->second.stocks["$DOW"].last)/first_dow;
prev_dow = it->second.stocks["$DOW"].last;
Tnasdaq = LD(it->second.stocks["$NASDAQ"].last)/first_nasdaq;
prev_nasdaq = it->second.stocks["$NASDAQ"].last;
Tsp500 = LD(it->second.stocks["$SP500"].last)/first_sp500;
prev_sp500 = it->second.stocks["$SP500"].last;
rr.day = it->first.day;
rr.month = it->first.month;
rr.year = it->first.year;
rr.myfund0 = 100*(Tmyfund-1);
rr.dow0 = 100*(Tdow-1);
rr.nasdaq0 = 100*(Tnasdaq-1);
rr.sp5000 = 100*(Tsp500-1);
returns.push_back(rr);
}
// calculate the tail-anchored one
long double fmyfund = 100 + returns[returns.size()-1].myfund0;
long double fdow = 100 + returns[returns.size()-1].dow0;
long double fcomp = 100 + returns[returns.size()-1].nasdaq0;
long double fsp500 = 100 + returns[returns.size()-1].sp5000;
for (size_t z = 0; z < returns.size(); ++z) {
returns[z].myfund1 = 100 * ((100 + returns[z].myfund0) / fmyfund - 1);
returns[z].dow1 = 100 * ((100 + returns[z].dow0) / fdow - 1);
returns[z].nasdaq1 = 100 * ((100 + returns[z].nasdaq0) / fcomp - 1);
returns[z].sp5001 = 100 * ((100 + returns[z].sp5000) / fsp500 - 1);
}
for (size_t z = 0; z < returns.size(); ++z) {
sprintf(returnsbuf, "%02d/%02d/%d %+11.2Lf %+11.2Lf %+11.2Lf %+11.2Lf %+11.2Lf %+11.2Lf %+11.2Lf %+11.2Lf %+11.2Lf %+11.2Lf %+11.2Lf %+11.2Lf",
returns[z].month, returns[z].day, returns[z].year,
returns[z].myfundd, returns[z].nasdaqd, returns[z].sp500d, returns[z].dowd,
returns[z].myfund0, returns[z].nasdaq0, returns[z].sp5000, returns[z].dow0,
returns[z].myfund1, returns[z].nasdaq1, returns[z].sp5001, returns[z].dow1);
returns_file << returnsbuf << endl;
}
tex_file << endl;
tex_file << endl;
char dividend_buf[10];
sprintf(dividend_buf, "%0.2lf", total_dividend);
tex_file << "\\vspace{1cm}" << endl;
tex_file << endl;
tex_file << "Total dividends: " << dividend_buf << endl;
tex_file << endl;
tex_file << endl;
tex_file << "}" << endl;
tex_file << endl;
tex_file << endl;
tex_file << "\\end{document}" << endl;
// finish off returns
returns_file << "#----------+-----------------------------------------------+-----------------------------------------------+-------------------------------------------------" << endl;
returns_file << "# | daily returns | cumulative returns | tail-anchored cumulative returns" << endl;
returns_file << "#----------+-----------------------------------------------+-----------------------------------------------+-------------------------------------------------" << endl;
returns_file << "# Date | Portfolio NASDAQ S&P 500 Dow Jones | Portfolio NASDAQ S&P 500 Dow Jones | Portfolio NASDAQ S&P 500 Dow Jones" << endl;
}
// ==================================== main ====================================
int main(int argc, char *argv[]) {
if (argc != 2) {
cerr << "Usage: assemble [output file]" << endl;
exit(0);
}
get_today_date();
tex_file.open(argv[1]);
prep_lookup();
init_portfolio();
prep_data();
load_prices();
compute_changes();
init_returns_file();
init_gnuplot();
print_data();
return 0;
}