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