// 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; }