/* Copyright (c) 2005-2026 Tachyon Design Automation Corp. */

/* === INSERT LICENSE ===
ib_ytd_anal program for analyzing Interactive Brokers format downloaded
Year to Date .csv format spread sheet file.

Copyright © 2005-2026 Tachyon Design Automation Corp.
All Rights reserved.  Licensed software subject to prohibitions and
restrictions.

The intent of this document is to state the conditions under which the
ib_ytd_anal trade analysis C program may be copied and executed such that
the Copyright Holder Tachyon Design Automation maintains distribution and
artistic control over the use and development as granted by Copyright law. 

Licensee may make modifications to ib_ytd_anal for internal use.  ib_ytd_anal
may not be sold without obtaining a sales license from Tachyon DA. 
All forms of indirect sales such as bundling or executing on a shared
cloud are prohibited.  Other uses of ib_ytd_anal program require written
permission from Tachyon Design Automation.

 DISCLAIMER OF WARRANTIES: THIS SOFTWARE IS PROVIDED "AS IS" AND
WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION,
THE IMPLIED WARRANTIES OF MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE,
OR NON-INFRINGEMENT.  THE COPYRIGHT HOLDER SHALL NOT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY
OUT OF THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.

=== END INSERT === */

/*
 * miscellaneous routines
 */

#include <stdio.h>
#include <time.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <pthread.h>
#include <ctype.h>
#include <math.h>

#include <pwd.h> 

#include "newport.h"

#ifdef __DBMALLOC__
#include "../malloc.h"
#endif

static char *cnvt_opt_strike_to_7digs(char *, double);
static int32 is_debcred_trans(struct trans_t *);
static int32  str_area_iswhite(char *, int32);
static int32 fnd_re_match_acru_trans(char *, struct trans_t *); 
static int32 is_white(char *);
static int32 fnd_symndx_end(char *, int32, int32 *);
static int32 cmp_pos_sortsyms(const void *, const void *);

extern void __init_prt_rpt_tot_flds(struct trans_t *);
extern void __init_symndx(struct symndx_t *);
extern void __add_to_end_trans(struct trans_t *);
extern void __add_to_end_symndx(struct symndx_t *);
extern void __bld_ndx_opos_trans(void);
extern void __dbg_dmp_symndx(char *);
extern void __dbg_dmp1_tradtr(int32);
extern int32 __fnd_trtab_by_trtyp(char *, int32, int32);
extern void __bld_ndx_sortsym(void);
extern int32 __bld_ib_csv_flds(void);
extern int32 __fnd_ch_in_trans_cod(char *, char);
extern char __cod_opcl_ch(char *);
extern struct trans_t *__fnd_posndx_oppos(char *, int32);
extern char *__int_to_acct_type_str(int32);
extern int32 __dash_num_date_to_datrec(char *, struct date_t *);
extern int32 __ib_time_to_clktim(char *, struct clktime_t *);
extern int32 __month_abbrev_to_num(char *);
extern char * __mth_num_to_fut_let(char *, int32);
extern char *__bld_sortsym(char *, struct trans_t *);
extern void __init_dat(struct date_t *);
extern int32 __cmp_dt(const struct date_t *, const struct date_t *);
extern int32 __cmp_clktim(const struct clktime_t *,
 const struct clktime_t *);
extern char *__dt_to_string(char *, struct date_t *);
extern void __init_clktim(struct clktime_t *);
extern char *__my_malloc(size_t);
extern void __msg(char *, ...);
extern void __misc_terr(char *, int32);
extern void __fwarn(int32, char *, ...);
extern void __ferr(int32, char *, ...);
extern void __terr(int32, char *, ...);
extern void __case_terr(char *, int32);
extern void __arg_terr(char *, int32);
extern void __my_vfprintf(FILE *, char *, va_list);

/*
 * INITIALIZATION ROUTINES
 */

/*
 * notice because using this ms file for reading and processing
 * need local static init procs
 */

/*
 * init a transaction record
 */
extern void __init_tran(struct trans_t *trp)
{
 int32 chi;

 if (strcmp(__brkrnam, "Ib") == 0) trp->acc_typ = ACCT_IB;
 else if (strcmp(__brkrnam, "Si") == 0) trp->acc_typ = ACCT_S_IRA;
 else if (strcmp(__brkrnam, "Sm") == 0) trp->acc_typ = ACCT_S_MARGIN;
 else __case_terr(__FILE__, __LINE__);

 trp->tr_typ = TRTYP_NONE;
 trp->tr_ityp = TRITYP_NONE;
 trp->is_buy = TRUE;
 /* SJM 01-25-22 all currencies are USD unless different in record */
 trp->cur_typ = CUR_USD;
 trp->is_buy = TRUE;
 trp->is_put_opt = FALSE;
 trp->fut_expmth = 0;
 trp->fut_expyr = 0;
 trp->op_last_yr = FALSE;
 trp->cl_tr_in_tots = FALSE;
 trp->tdy_pseudo_cl = FALSE;

 /* SJM 02-15-22 - set all chars to 0 have dummy trp2 previous now */
 for (chi = 0; chi < MAXFLDSIZ; chi++) trp->symbol[chi] = '\0';
 strcpy(trp->sort_sym, "");
 for (chi = 0; chi < MAXFLDSIZ; chi++) trp->sort_sym[chi] = '\0';

 strcpy(trp->tr_code, "");
 __init_dat(&(trp->tdate));
 __init_clktim(&(trp->ttime));
 trp->tr_quant = 0.0;
 trp->tr_price = 0.0;
 trp->tr_tdy_closepr = 0.0;
 trp->tr_yday_closepr = 0.0;
 trp->tr_comm = 0.0;
 trp->tr_amount = 0.0;
 trp->op_unrlz_gain = 0.0;
 trp->trcl_ndxi = -1;
 trp->op_tdy_trcl_ndxi = -1;
 trp->opt_strike = 0.0; 
 __init_dat(&(trp->opt_exp_dt));
 trp->tr_mult = 0.0;
 trp->tr_xdiv_amt = 0.0;
 __init_dat(&(trp->xdiv_dt));
 __init_dat(&(trp->div_pay_dt));
 trp->not_cl_share_no = 0.0;
 trp->this_still_op = 0.0;
 /* this is in cl trade tran avg opening pr of this close */
 trp->op_avg_sh_pr = 0.0;
 trp->cl_gain_per_sh = 0.0;
 for (chi = 0; chi < MAXFLDSIZ; chi++)
  trp->trans_desc_string[chi] = '\0';
 for (chi = 0; chi < MAXFLDSIZ; chi++) trp->trline[chi] = '\0';
 trp->lno = -1;
}

/*
 * init a sym ndx record
 */
extern void __init_symndx(struct symndx_t *sip)
{
 int32 mi;

 sip->sip_bas = FALSE;
 sip->in_sipgrp = FALSE;
 sip->ndxsnamp = NULL;
 sip->tr_bi = -1;
 sip->tr_ei = -1;
 sip->bas_sgrp_ndxi = -1;
 /* since one sip is either stk, fut or opt rlz gain for any */
 sip->rlzgain_tot = 0;
 for (mi = 0; mi <= 12; mi++) sip->mth_rlzgain[mi] = 0.0;
 sip->rlz_traded_shares = 0;
 sip->rlz_traded_opts = 0;
 sip->ytd_tot_divs = 0;
 for (mi = 0; mi <= 12; mi++) sip->mth_divs[mi] = 0.0;
 sip->unrlz_gain = 0;
 sip->opt_unrlz_gain = 0;
 sip->opt_pot_income = 0;
 sip->open_stk_acru_div = 0;
 __init_dat(&(sip->op_xdiv_dt));
 __init_dat(&(sip->op_div_pay_dt));

 sip->bas_open_opt_num = 0;
 sip->num_bas_ins = 0;
 sip->bas_has_options = FALSE;
 sip->bas_rlzgain_tot = 0;
 for (mi = 0; mi <= 12; mi++) sip->bas_mth_rlzgain[mi] = 0;
 sip->bas_opt_rlzgain_tot = 0;
 for (mi = 0; mi <= 12; mi++) sip->bas_opt_mth_rlzgain[mi] = 0;
 sip->bas_rlz_traded_shares = 0;
 sip->bas_rlz_traded_opts = 0;
 sip->bas_all_unrlz_gain = 0;
 sip->bas_op_opt_unrlz_gain = 0;
 sip->bas_investment_tot = 0;
 sip->bas_opt_pot_income = 0;
 sip->bas_ldiv_persh = 0;
 __init_dat(&(sip->bas_last_div_pay_dt));
}

/*
 * add an alloced tran to tran tab
 */
extern void __add_to_end_trans(struct trans_t *trp2)
{
 struct trans_t *trp;

 if (++__last_trani >= SIZ_TRAN_TAB) __misc_terr(__FILE__, __LINE__);   
 trp = (struct trans_t *) __my_malloc(sizeof(struct trans_t));
 *trp =  *trp2;
 __trantab[__last_trani] = trp;
}

/*
 * ROUTINES TO BUILD AND FILL SORT SYM INDEX RECORD FIELDS
 */

/*
 * build sym ndx one entry for each sort sym that has a trade record in
 * tran tab
 */
extern void __bld_ndx_sortsym(void)
{
 int32 tri, begi, endi, is_endoftran; 
 struct trans_t *trp;
 struct symndx_t *sip, wrk_symndx;

 __last_symndxi = -1;
 for (tri = 0; tri <= __last_trani;) 
  {
   trp = __trantab[tri];
   if (trp->tr_typ != TRTYP_TRADE) { tri++; continue; }
   /* have tr - if more trs for sort sym, know contiguous */
   begi = tri;
   endi = fnd_symndx_end(trp->sort_sym, begi, &(is_endoftran));
   sip = &(wrk_symndx);
   __init_symndx(sip);
   sip->ndxsnamp = &(trp->sort_sym);
   sip->tr_bi = begi;
   sip->tr_ei = endi;
   __add_to_end_symndx(sip);
   if (is_endoftran) break;
   tri = endi + 1;
  }
}

/*
 * given a sort symbol index in tran tab find end index 
 * know begi points to trade record with sort sym sym
 */
static int32 fnd_symndx_end(char *sym, int32 begi, int32 *eoftrantab)
{
 struct trans_t *trp;
 int32 tri;

 *eoftrantab = FALSE;
 /* assume one trade */
 for (tri = begi + 1; tri <= __last_trani; tri++)
  {
   trp = __trantab[tri];
   if (trp->tr_typ != TRTYP_TRADE) return(tri - 1);
   if (strcmp(sym, trp->sort_sym) != 0) return(tri - 1);
  }
 /* got to end */
 *eoftrantab = TRUE;
 return(tri - 1);
}

/*
 * add an alloced symndx record symndx tab
 */
extern void __add_to_end_symndx(struct symndx_t *symndxp)
{
 struct symndx_t *sip;

 if (++__last_symndxi >= SIZ_SYMNDX) __misc_terr(__FILE__, __LINE__);   
 sip = (struct symndx_t *) __my_malloc(sizeof(struct symndx_t));
 *sip =  *symndxp;
 __symndx[__last_symndxi] = sip;
}

/*
 * ROUTINES TO BUILD THE POS NDX OF TRAN RECORDS INDEX 
 */

/*
 * bld separate ndx of all open pos tran records 
 *
 * SJM 05-05-25 - assuming there will be open pos records not
 * corresponding to sip open pos base symbols - is it true?
 */
extern void __bld_ndx_opos_trans(void)
{
 int32 tri;
 struct trans_t *trp;

 /* build a tab of ptrs to oppos type records */
 __last_posndxi = -1;   
 for (tri = 0; tri <=__last_trani; tri++)
  {
   trp = __trantab[tri];
   /* since subset do not need to check for over flow */
   if (trp->tr_typ == TRTYP_OPPOS)
    {
     __posndx[++__last_posndxi] = trp;
     continue;
    }
  }
 qsort((void *) __posndx, __last_posndxi + 1, sizeof(struct trans_t *),
  cmp_pos_sortsyms);

/* DBG add ===
 {
  int32 opi;

  for (opi = 0; opi <= __last_posndxi; opi++) 
   {
    trp = __posndx[opi];
    __msg(" ++postab DBG: %s : op quant=%.3f op pr=%.3f\n",
     trp->sort_sym, trp->tr_quant, trp->tr_price);
   }
 }
==== */
}

/*
 * sort oppos record subset by sort sym
 */
static int32 cmp_pos_sortsyms(const void *p1, const void *p2)
{
 struct trans_t *trp1, *trp2;

 trp1 = (*((struct trans_t **) p1)); 
 trp2 = (*((struct trans_t **) p2)); 
 return(strcmp(trp1->sort_sym, trp2->sort_sym));
}

/*
 * debug dmp sym ndx
 */
extern void __dbg_dmp_symndx(char *s)
{
 int32 si, si2, tri, bsi;
 struct symndx_t *sip, *sip2;
 struct trans_t *trp, *trp2;

 __msg("\n *** symndx dump at %s\n", s);
 for (si = 0; si <= __last_symndxi; si++)
  {
   sip = __symndx[si];
   __msg(
   "DBG: sym %s bi=%d ei=%d rlzgain_tot=%.3f ytd_divs=%.3f\n",
   sip->ndxsnamp, sip->tr_bi, sip->tr_ei, sip->rlzgain_tot,
    sip->ytd_tot_divs);
   for (tri = sip->tr_bi; tri <= sip->tr_ei; tri++) 
    {
     trp = __trantab[tri];
     __dbg_dmp1_tradtr(tri);
/* ====
     else 
      {
       if (trp->is_buy) strcpy(s1, "buy");
       else strcpy(s1, "sell");
       __msg("ABBRV TR: %s %s (%s) %s  %02d\n", s1,
        trp->sort_sym, __dt_to_string(s2, &(trp->tdate)),
        trp->tr_code, trp->tr_ityp);
       }
==== */
    }
  /* dump the bas sym group from sip marked as sip bas */
  if (!sip->sip_bas) continue;

  if (sip->bas_sgrp_ndxi == -1)
   {
    __msg("*** NO BASE SYMBOL GROUP FOR %s***\n", sip->ndxsnamp);
   }
  else
   {
    __msg("SIP BAS SYM %s SORT SYM %s;\n", trp->symbol,
     trp->sort_sym); 
    for (bsi = sip->bas_sgrp_ndxi + 1;; bsi++)
     {
      si2 = __basgrpndx[bsi];
      if (si2 == -1) break;
      sip2 = __symndx[si2];
      trp2 = __trantab[sip2->tr_bi];
      __msg("  IN GRP SIP BAS SYM %s SORT SYM %s;\n", trp2->symbol,
      trp2->sort_sym);
     }
   }
  }
}

/*
 * debug dmp 1 trade trans record
 */
extern void __dbg_dmp1_tradtr(int32 tri)
{
 struct trans_t *trp;
 char s1[RECLEN], s2[RECLEN];

 trp = __trantab[tri];
 /* DBG remove === */
 if (trp->tr_typ != TRTYP_TRADE) __misc_terr(__FILE__, __LINE__);
 /* === */
 if (trp->is_buy) strcpy(s1, "buy"); else strcpy(s1, "sell");
 __msg(
  "TR %0d: %s %s on %s tr_ityp=%d cur_typ=%d is_put=%d crossyr=%d\n",
  tri, s1, trp->sort_sym, __dt_to_string(s2, &(trp->tdate)),
  trp->tr_ityp, trp->cur_typ, trp->is_put_opt, trp->op_last_yr); 
 __msg(
  "    cod=%s, quant=%.2lf pr=%.2lf tdy-cl=%.2lf not cl_no=%.2lf\n",
  trp->tr_code, trp->tr_quant, trp->tr_price, trp->tr_tdy_closepr,
  trp->not_cl_share_no);
 __msg( "    op_avg_shr_pr=%2lf tr_mult=%.2lf\n",
  trp->op_avg_sh_pr, trp->tr_mult);
 __msg(
 "    xdiv_amt=%.2lf still op=%.2lf rlz per sh gain=%.2lf\n",
  trp->tr_xdiv_amt, trp->this_still_op, trp->cl_gain_per_sh);
}

/*
 * ROUTINES TO GET TRANSACTION PROPERTIES
 */

/*
 * adjust by multiplying by symbol multiplier
 */
extern double __smult(struct trans_t *trp)
{
 if (trp->tr_ityp == TRITYP_STOCK) return(1.0);
 if (trp->tr_ityp == TRITYP_OPT) return(100.0);
 if (trp->tr_ityp == TRITYP_FUT || trp->tr_ityp == TRITYP_FUTOPT)
  { 
   if (strcmp(trp->symbol, "MES") == 0) return(5.0);
   if (strcmp(trp->symbol, "ES") == 0) return(50.0);
   if (strcmp(trp->symbol, "M2K") == 0) return(5.0);
   if (strcmp(trp->symbol, "MNQ") == 0) return(2.0);
   if (strcmp(trp->symbol, "CL") == 0) return(1000.0);
   if (strcmp(trp->symbol, "MCL") == 0) return(100.0);
   if (strcmp(trp->symbol, "QG") == 0) return(2500.0);
   if (strcmp(trp->symbol, "CAD") == 0) return(100000.0);
   // SJM 05-20-25 6C is maybe internal form of CAD same 100k
   if (strcmp(trp->symbol, "6C") == 0) return(100000.0);
   if (strcmp(trp->symbol, "AUD") == 0) return(100000.0);
   if (strcmp(trp->symbol, "6A") == 0) return(100000.0);
   if (strcmp(trp->symbol, "MCD") == 0) return(10000.0);
   if (strcmp(trp->symbol, "MSF") == 0) return(10000.0);
   if (strcmp(trp->symbol, "GBP") == 0) return(62500.0);
   if (strcmp(trp->symbol, "6B") == 0) return(62500.0);
   if (strcmp(trp->symbol, "JPY") == 0) return(12500000.0);
   if (strcmp(trp->symbol, "J7") == 0) return(6250000.0);
   if (strcmp(trp->symbol, "M6A") == 0) return(10000.0);
   if (strcmp(trp->symbol, "M6B") == 0) return(6250.0);
   if (strcmp(trp->symbol, "EUR") == 0) return(125000.0);
   if (strcmp(trp->symbol, "M6E") == 0) return(12500.0);
   if (strcmp(trp->symbol, "6E") == 0) return(125000.00);
   if (strcmp(trp->symbol, "E7") == 0) return(62500.00);
   if (strcmp(trp->symbol, "J7") == 0) return(6250000.00);
   if (strcmp(trp->symbol, "GC") == 0) return(100.0);
   if (strcmp(trp->symbol, "MGC") == 0) return(10.0);
   if (strcmp(trp->symbol, "HG") == 0) return(25000.0);
   if (strcmp(trp->symbol, "MHG") == 0) return(2500.0);
   if (strcmp(trp->symbol, "NG") == 0) return(10000.0);
   if (strcmp(trp->symbol, "MNG") == 0) return(1000.0);
   if (strcmp(trp->symbol, "XK") == 0) return(1000.0);
   if (strcmp(trp->symbol, "YK") == 0) return(1000.0);
   if (strcmp(trp->symbol, "ZS") == 0) return(5000.0);
   if (strcmp(trp->symbol, "ZW") == 0) return(5000.0);
   if (strcmp(trp->symbol, "ZC") == 0) return(5000.0);
   if (strcmp(trp->symbol, "YW") == 0) return(1000.0);
   if (strcmp(trp->symbol, "XW") == 0) return(1000.0);
   if (strcmp(trp->symbol, "YC") == 0) return(1000.0);
   if (strcmp(trp->symbol, "XC") == 0) return(1000.0);
   if (strcmp(trp->symbol, "ZM") == 0) return(100.0);
   if (strcmp(trp->symbol, "ZL") == 0) return(600.0);
   if (strcmp(trp->symbol, "ZN") == 0) return(1000.0);
   if (strcmp(trp->symbol, "ZB") == 0) return(1000.0);
   if (strcmp(trp->symbol, "S10Y") == 0) return(100.0);
   if (strcmp(trp->symbol, "S2Y") == 0) return(100.0);
   if (strcmp(trp->symbol, "2YY") == 0) return(1000.0);
   if (strcmp(trp->symbol, "10Y") == 0) return(1000.0);
   if (strcmp(trp->symbol, "MET") == 0) return(0.1);
   __misc_terr(__FILE__, __LINE__);
  }
 return(1.0);
}

/*
 * get the no. of digits that correspond with smult mulipliers
 */
extern int32 __get_fld_digs(struct trans_t *trp)
{
 int32 mul, ndigs;

 mul = __smult(trp);
 ndigs = 2;
 if (mul >= 1000.0) ndigs = 3;
 if (mul >= 5000.0) ndigs = 4;
 if (mul >= 6250000.00) ndigs = 6;
 return(ndigs);
}

/*
 * find a tran record by searching for sort sym in open position pos ndx
 *:
 * SJM 05-05-25 - LOOKATME could binary search this
 */
extern struct trans_t *__fnd_posndx_oppos(char *sym,
 int32 use_sortsym)
{
 int32 pi;
 struct trans_t *trp;

 for (pi = 0; pi <= __last_posndxi; pi++) 
  {
   trp = __posndx[pi];
   /* DBG remove -- */
   if (trp->tr_typ != TRTYP_OPPOS) __misc_terr(__FILE__, __LINE__);
   /* --- */
   if (use_sortsym)
    {
     if (strcmp(sym, trp->sort_sym) == 0) return(trp);
    } 
   else 
    {
     if (strcmp(sym, trp->sort_sym) == 0) return(trp);
    }
  }
 return(NULL);
}


/*
 * ROUTINES TO TAKE APART CSV LINES
 */

/*
 * read an ib report .csv file line and extract fields (always , separated)
 * return EOF (-1) on error (EOF) and 0 on success
 *
 * reads into cur line and then fill already allocated large enough csvfldtab
 */
extern int32 __rd_xtrct_ib_csv_line(FILE *f, int32 needed)
{
 int32 i;
 char s1[RECLEN];

 if ((fgets(__cur_line, RECLEN, f)) == NULL)
  {
   if (needed)
    {
     __misc_terr(__FILE__, __LINE__);
    }
   return(EOF);
  }
 /* SJM 09-13-20 for some reason first line of IB .csv has 3 hex chars */
 for (i = 0;; i++)
  {
   if (isprint(__cur_line[i])) break;
  }
 strcpy(s1, &(__cur_line[i]));
 strcpy(__cur_line, s1);
 
 __line_cnt++;
 __bld_ib_csv_flds();
 return(0);
}

/*
 * isolate the IB .csv report file passed line into global
 * returns F on error else T - this emits errors and warnings
 *
 * this uses global __csvfldtab array of ptrs to isolated CSV fields
 * assume fields never bigger than MAXFLDSIZ bytes
 *
 * SJM 07-08-22 - added 512 char max fld size because gdb said 64k records
 * too big
 *
 * notice ib .csv files are not quoted so double quote in fields is just data
 * but comma is always field seperator not data
 */
extern int32 __bld_ib_csv_flds(void)
{
 int32 len;
 char *chp, *chp2;

 __lastcsvfld = -1;
 chp = __cur_line;
 for (;;)
  {
   /* SJM 12-26-20 - separator for date field is ending double quote */
   if (*chp == '"')
    {
     chp++;
     if ((chp2 = strchr(chp, '"')) == NULL) __misc_terr(__FILE__, __LINE__);
     strncpy(__csvfldtab[++__lastcsvfld], chp, chp2 - chp);
     __csvfldtab[__lastcsvfld][chp2 - chp] = '\0'; 
     chp2++;
     /* check one after end double quote - if last fld exit loop */
     if (*chp2 == '\n' || *chp2 == '\0') break;
     if (*chp2 != ',') __misc_terr(__FILE__, __LINE__);
     chp = ++chp2; 
     continue; 
    }
   if ((chp2 = strchr(chp, ',')) == NULL)
    {
     strcpy(__csvfldtab[++__lastcsvfld], chp);
     len = strlen(chp);
     if (__csvfldtab[__lastcsvfld][len - 1] == '\n') 
      {
       __csvfldtab[__lastcsvfld][len - 1] = '\0'; 
      }
     break;
    }
   if (chp2[1] == '\0') 
    {
     /* think last field can end with , soemtimes */
     chp2[0] = '\0';
     strcpy(__csvfldtab[++ __lastcsvfld], chp);
     break;
    }
   strncpy(__csvfldtab[++ __lastcsvfld], chp, chp2 - chp + 1);
   /* , ends field, convert to EOS */
   __csvfldtab[__lastcsvfld][chp2 - chp] = '\0';
   chp = ++chp2;
  }
 return(0);
}

/*
 * isolate the Schwab .CSV report file passed line into global tab
 * returns FALSE on error else TRUE - error emitted here
 *
 * Notice for Schwab simple .CSV files, building csv fld tab all done here
 * because there is only one type of line with fields always quoted
 *
 * this uses global __csvfldtab array of ptrs to isolated .CSV fields
 * assume fields never bigger than MAXFLDSIZ bytes
 *
 * this is for Schwab style .CSV files that 
 * caller removes <cr> or <nl> at end of line so ends with \0
 */
extern int32 __rd_bld_schwab_quoted_csv_flds(FILE *f)
{
 int32 flen;
 char *wrkbufp, *chp2;

 if ((fgets(__cur_line, RECLEN, f)) == NULL)
  {
   return(EOF);
  }
 
 __line_cnt++;

 __lastcsvfld = -1;
 wrkbufp = __cur_line;
 /* empty lines legal and just return with empty field tab */
 if (wrkbufp[0] == '\r' && wrkbufp[1] == '\0') return (TRUE);
 if (wrkbufp[0] == '\n') return(TRUE);

 for (;;)
  {
   /* not sure if leading white space allow before fields - emit warning */
   if (wrkbufp[0] != '"')
    {
     __fwarn(101, "quoted field does not start with double quote");
     if ((chp2 = strchr(wrkbufp, '"')) == NULL)
      {
       __fwarn(102,
        "quoted field no. %d (line pos. %d) - trailing characters ignored",
        __lastcsvfld + 1, wrkbufp - __cur_line);
       break;
      }
     flen = chp2 - wrkbufp;
     if (!str_area_iswhite(wrkbufp, flen))
      {
       __fwarn(103,
        "quoted field no. %d (line pos %d) non white  before double quote",
        __lastcsvfld + 1, wrkbufp - __cur_line);
      }
     else
      {
       __fwarn(104,
        "quoted field %d (line pos %d) white space  before double quote",
        __lastcsvfld + 1, wrkbufp - __cur_line); 
      }
     wrkbufp = chp2; 
    }
   /* now wrk buf points to field starting double quote */
   wrkbufp++;
   if ((chp2 = strchr(wrkbufp, '"')) == NULL)
    {
     __misc_terr(__FILE__, __LINE__);
     return(FALSE);
    }
   *chp2 = '\0';
   strcpy(__csvfldtab[++__lastcsvfld], wrkbufp);
   if (chp2[1] == '\0') break;
   wrkbufp = &(chp2[1]);
  }
 return(TRUE);
}

/*
 * scan a numeric csvfld and if "" (empty) return 0.0
 */
extern double __get_num_csvfld_val(char *flds, int32 no_dol_pref)
{
 double rv;

 if (strcmp(flds, "") == 0) return(0.0);
 if (sscanf(flds, "$%lf", &(rv)) != 1) __misc_terr(__FILE__, __LINE__);
 return(rv);
}

/*
 * return T if string area is all white space else F
 */
static int32  str_area_iswhite(char *chp, int32 nchars)
{
 int32 i;

 for (i = 0; i < nchars; i++, chp++)
  {
   if (!is_white(chp)) return(FALSE); 
  }
 return(TRUE);
}

/*
 * return T if is white space (space and tab - others 
 */
static int32 is_white(char *chp)
{
 if (*chp == ' ' || *chp == '\t') return(TRUE);
 return(FALSE);
}

/*
 * TRANSACTION TYPE FIELD MAPPING EQUIVALENCE TABLES AND ROUTINES
 */

/* map currencies back and forth */

static struct map_t cur_type[] = {
 { "NONE", CUR_NONE },
 { "USD", CUR_USD },
 { "CAD", CUR_CAD },
 { "GBP", CUR_GBP }
};
#define NUM_CUR_TYPES (sizeof(cur_type) / sizeof(struct map_t))

extern int32 __cur_type_to_int(char *s)
{
 int32 i;
  
 for (i = 0; i < NUM_CUR_TYPES; i++)
  {
   if (strcmp(s, cur_type[i].str) == 0) return(cur_type[i].mval);  
  }
 return(-1);
}

extern char *__int_to_cur_type_str(int32 ctyp)
{
 int32 i;

 for (i = 0; i < NUM_CUR_TYPES; i++)
  {
   if (cur_type[i].mval == ctyp) return(cur_type[i].str);
  }
 return(NULL);
}

/* map to and from account type - broker at */
static struct map_t acct_type[] = {
 { "Ib", ACCT_IB },
 { "Si", ACCT_S_IRA },
 { "Sm", ACCT_S_MARGIN }
};
#define NUM_ACCT_TYPES (sizeof(acct_type) / sizeof(struct map_t))

extern int32 __acct_type_to_int(char *s)
{
 int32 i;
  
 for (i = 0; i < NUM_ACCT_TYPES; i++)
  {
   if (strcmp(s, acct_type[i].str) == 0) return(acct_type[i].mval);  
  }
 return(-1);
}

extern char *__int_to_acct_type_str(int32 atyp)
{
 int32 i;

 for (i = 0; i < NUM_ACCT_TYPES; i++)
  {
   if (acct_type[i].mval == atyp) return(acct_type[i].str);
  }
 return(NULL);
}

/* map to tran type name - trade etc. - since search does not need all */
static struct map_t tran_typ[] = {
 { "OpPos", TRTYP_OPPOS },
 { "Tr", TRTYP_TRADE },
 { "Div", TRTYP_DIV },
 { "inLdiv", TRTYP_INLIEU_DIV },
 { "divAcru", TRTYP_DIVACRU },
 { "Fee", TRTYP_FEE },
 { "Tax", TRTYP_TAX },
 { "Dep", TRTYP_DEPOSIT },
 { "Withd", TRTYP_WITHDRAW },
 { "Int", TRTYP_INTEREST },
 { "Bal", TRTYP_BALANCE }
};
#define NUM_TR_TYPES (sizeof(tran_typ) / sizeof(struct map_t))

extern int32 __tr_type_to_int(char *s)
{
 int32 i;
  
 for (i = 0; i < NUM_TR_TYPES; i++)
  {
   if (strcmp(s, tran_typ[i].str) == 0) return(tran_typ[i].mval);  
  }
 return(-1);
}

extern char *__int_to_tr_type_str(int32 ttyp)
{
 int32 i;

 for (i = 0; i < NUM_TR_TYPES; i++)
  {
   if (tran_typ[i].mval == ttyp) return(tran_typ[i].str);
  }
 return(NULL);
}

/* instrument type - buy/sell indicated by +quant or -quant */
/* check and wire are now sym names */ 
static struct map_t tr_ityp[] = {
 { "Stk", TRITYP_STOCK }, 
 { "Fut", TRITYP_FUT },
 { "Opt", TRITYP_OPT },
 { "FOpt", TRITYP_FUTOPT },
 { "Cr", TRITYP_CREDIT },
 { "Db", TRITYP_DEBIT },
 { "BalCash", TRITYP_BAL_CASH },
 { "BalAcct", TRITYP_BAL_ACCT }
};
#define NUM_INS_TYPES (sizeof(tr_ityp) / sizeof(struct map_t))

/*
 * map from instrument type to and from name
 */
extern int32 __trityp_to_int(char *s)
{
 int32 i;
  
 for (i = 0; i < NUM_INS_TYPES; i++)
  {
   if (strcmp(s, tr_ityp[i].str) == 0)
    {
     return(tr_ityp[i].mval);  
    }
  }
 return(-1);
}

extern char *__int_to_ityp_str(int32 ttyp)
{
 int32 i;

 for (i = 0; i < NUM_INS_TYPES; i++)
  {
   if (tr_ityp[i].mval == ttyp) return(tr_ityp[i].str);
  }
 return(NULL);
}

/*
 * AFTER TRANS RECORD FILLED BUILD OPTION AND FUTURE SYMBOL
 */

/*
 * build a sort sym not full sym - reading easy - could reformat for
 * printing if need easier to read
 */
extern char *__bld_sortsym(char *s, struct trans_t *trp)
{
 char mthlet, mnum;
 char *chp, s1[RECLEN], s2[RECLEN], s3[RECLEN];

 if (trp->tr_ityp == TRITYP_FUT)
  {
   /* SJM 11/19/22 - need to fix scrubbing of new yield based CME futures */
   if (strncmp(trp->symbol, "2YY", 3) == 0
    || strncmp(trp->symbol, "5YY", 3) == 0
    || strncmp(trp->symbol, "10Y", 3) == 0
    || strncmp(trp->symbol, "30Y", 3) == 0)
    {
     /* need something like 2YYZ2 */
     strcpy(s1, trp->symbol);
     strncpy(trp->symbol, s1, 3);
     trp->symbol[3] = '\0';
     chp = &(s1[5]);
     strncpy(s2, chp, 3);
     s2[3] = '\0';
     mnum = __month_abbrev_to_num(s2);
     trp->fut_expmth = mnum;
    }
   __mth_num_to_fut_let(s3, trp->fut_expmth);
   mthlet = s3[0];
   // <sym><yy><fut_mth let>
   sprintf(s1, "%s%02d%c", trp->symbol, trp->fut_expyr % 2000, mthlet);
   strcpy(s, s1);
   return(s);
  }
 if (trp->tr_ityp == TRITYP_OPT)
  {
   if (trp->is_put_opt) strcpy(s1, "P"); else strcpy(s1, "C");
   cnvt_opt_strike_to_7digs(s2, trp->opt_strike);
   /* symbol is underlying symbol */
   // SPXP00019500
   // <sym><yy><mm><dd><C|P>00019500
   sprintf(s, "%s_%02d%02d%02d%s%s", trp->symbol, trp->opt_exp_dt.year,
    trp->opt_exp_dt.month, trp->opt_exp_dt.day, s1, s2);
   return(s);
  }
 if (trp->tr_ityp == TRITYP_FUTOPT) 
  {
   /* SJM - FIXME - need table especially for ES (MES) that change to */
   /* right symbol for series like expire on Monday etc. */
   if (trp->is_put_opt) strcpy(s1, "P"); else strcpy(s1, "C");
   cnvt_opt_strike_to_7digs(s2, trp->opt_strike);
   /* symbol is underlying symbol */
   // SPX141122P00019500
   // <sym><yy><mm><dd><C|P>00019500
   //  SJM 07-20-22 - to match trade record exp month is same as opt 
   __mth_num_to_fut_let(s3, trp->opt_exp_dt.month);
   mthlet = s3[0];
   sprintf(s, "%s%02d%c_%02d%02d%02d%s%s", trp->symbol,
    trp->opt_exp_dt.year % 2000, mthlet, trp->opt_exp_dt.year,
    trp->opt_exp_dt.month, trp->opt_exp_dt.day, s1, s2);
   return(s);
  }
 /* for all other full sym is same as sym */
 strcpy(s, trp->symbol);
 return(s);
}

/*
 * convert stock option strike price to 5 integer and 2 fraction as
 * ending 2 digits (00019500) is 19.50 strick
 *
 * SJM 12-21-21 - for now using same 2 frac digits for future options
 */
static char *cnvt_opt_strike_to_7digs(char *s, double op_strike)
{
 int32 dig2val; 

 dig2val = (int32) (op_strike*100.0 + 0.05); 
 sprintf(s, "%08d", dig2val);
 return(s);
}

/*
 * build a stock or future option symbol
 *
 * stock is "AGNC_112020_14.50C"
 * future is "CLF1_112720_37.00P"
 */
extern char *__bld_opt_sym(char *osym, struct trans_t *trp)
{
 char otypstr[RECLEN], s1[RECLEN];

 if (trp->tr_ityp != TRITYP_OPT) __arg_terr(__FILE__, __LINE__);

 if (trp->is_put_opt) strcpy(otypstr, "P"); else strcpy(otypstr, "C");
 sprintf(s1, "%s_%02d%02d%02d_%.2lf%s", trp->symbol, trp->opt_exp_dt.month,
  trp->opt_exp_dt.day, trp->opt_exp_dt.year - 2000, trp->opt_strike, otypstr);
 strcpy(osym, s1);
 return(osym);
}

/*
 * ROUTINES TO SEARCH TRANS RECORD TO MATCH FIELD
 */

/*
 * search a trans tab for symbol if req tr typ not -1 must of tran typ 
 *
 * this look for base symbol for options
 */
extern int32 __fnd_trtab_by_trtyp(char *s, int32 req_trtyp,
 int32 is_sortsym)
{
 int32 tri;
 struct trans_t *trp;

 for (tri = 0; tri <= __last_trani; tri++) 
  {
   trp = __trantab[tri];
   if (req_trtyp != -1 && req_trtyp != trp->tr_typ) continue;
   if (is_sortsym)
    {
     if (strcmp(trp->sort_sym, s) == 0) return(tri);
    }
   else if (strcmp(trp->symbol, s) == 0) return(tri); 
  }
 return(-1);
}

/*
 * find first unpaird Po/Re dividend acrual to find unpaid
 * since only for stock use base symbol 
 * 
 * LOOKATME - assuming xdiv records always paid - what if not paid?
 */
extern int32 __fnd_1st_unpaired_acru_trans(char *sym)
{
 int32 potri, retri;
 struct trans_t *potrp;

 for (potri = 0; potri <= __last_trani; potri++) 
  {
   potrp = __trantab[potri];
   if (potrp->tr_typ != TRTYP_DIVACRU) continue;
   if (strcmp(potrp->symbol, sym) != 0) continue;
   if (strcmp(&(potrp->tr_code[0]), "Po") != 0) continue;
    /* found an xdiv acru Po record matching sym */
   retri = fnd_re_match_acru_trans(sym, potrp);
   if (retri == -1) break;
  } 
 /* if past end, did not find an unmatched - either all paid or no divs */
 if ( potri > __last_trani) return(-1);
 /* this is the unmatch xdiv acru record - so open */
 return(potri);
}

/*
 * find a Re acro record match passed Po record
 */
static int32 fnd_re_match_acru_trans(char *sym, struct trans_t *potrp) 
{
 int32 retri, cv1, cv2; 
 struct trans_t *retrp;

 for (retri = 0; retri <= __last_trani; retri++)
  {
   retrp = __trantab[retri];
    if (retrp->tr_typ != TRTYP_DIVACRU) continue;
    if (strcmp(retrp->symbol, sym) != 0) continue;
    if (strcmp(&(retrp->tr_code), "Re") != 0) continue;

    /* SJM 12-03-25 - BEWARE IB Re pay acru reverse may change pay dt */
    /* only xdiv date same */
    cv1 = __cmp_dt(&(retrp->xdiv_dt), &(potrp->xdiv_dt));
    if (cv1 == 0) return(retri);
  }
 /* not paired - an acru not paid yet */
 return(-1);
}

// DCLME
/*
 * find latest dividend paid record 
 * since only for stock use base symbol 
 */
extern int32 __fnd_latest_div_pay_trans(char *sym) 
{
 int32 tri, ltri, cv;
 struct trans_t *trp;
 struct date_t latest_dt;

 __init_dat(&(latest_dt));
 ltri = -1;
 for (tri = 0; tri <= __last_trani; tri++) 
  {
   trp = __trantab[tri];
   if (trp->tr_typ != TRTYP_DIVACRU) continue;
   if (strcmp(trp->symbol, sym) != 0) continue;
   if (strcmp(&(trp->tr_code), "Re") != 0) continue;
   if (ltri == -1)
    {
     ltri = tri;
     latest_dt = trp->div_pay_dt;
     continue; 
    } 
   cv = __cmp_dt(&(trp->div_pay_dt), &(latest_dt)); 
   if (cv < 0) continue;
   ltri = tri;
   latest_dt = trp->div_pay_dt;
  }
 return(ltri);
}

/*
 * find a symndx entry for passed base symbol
 * this returns the stk/fut underlying, but if none returns 1st option
 */
extern struct symndx_t *__fnd_bassym_symndx(char *bassym)
{
 int32 si;
 struct symndx_t *sip;
 struct trans_t *trp;

 for (si = 0; si <= __last_symndxi; si++) 
  {
   sip = __symndx[si];
   trp = __trantab[sip->tr_bi];
   /* sip needs to be for underlying stock */
   if (strcmp(bassym, trp->symbol) == 0 && sip->sip_bas) return(sip);
  }
 return(NULL);
}

/*
 * find a symndx entry for passed sort symbol
 */
extern struct symndx_t *__fnd_sortsym_symndx(char *sortsym)
{
 int32 si;
 struct symndx_t *sip;
 struct trans_t *trp;

 for (si = 0; si <= __last_symndxi; si++) 
  {
   sip = __symndx[si];
   trp = __trantab[sip->tr_bi];
  if (strcmp(sortsym, trp->sort_sym) == 0) return(sip);
  }
 return(NULL);
}

/*
 *  return T if is open and F if a closing trade (only 2 possibilities)
 */
extern int32 __trade_is_open(char *cod)
{
 int32 rv, rv2;

 rv = __fnd_ch_in_trans_cod(cod, 'O');
 if (rv >= 1)
  {
   /* if O better not be C */
   /* SJM 09-18-22 - if "A;C:O" then sold part of assigned but some still op */ 
   rv = __fnd_ch_in_trans_cod(cod, 'C');
   if (rv >= 1)
    {
     rv2 = __fnd_ch_in_trans_cod(cod, 'A'); 
     if (rv2 >= 1) return(TRUE);
     __misc_terr(__FILE__, __LINE__);
    }
   return(TRUE);
  }
 /* if trade, and not O better be C */
 rv = __fnd_ch_in_trans_cod(cod, 'C');
 if (rv < 1) __misc_terr(__FILE__, __LINE__);
 return(FALSE);
}

/*
 * check to see if a given char (probably O for open or C for close)
 * in a trans code
 *
 * return 0 (false if not found) - return 1 if only 1 code letter else 2
 */
extern int32 __fnd_ch_in_trans_cod(char *codstr, char fndcod)
{
 char *chp;

 for (chp = codstr; *chp != '\0'; chp++)
  {
   if (*chp == fndcod)
    {
     if (strlen(codstr) == 1) return(1);
     return(2);
    }
  }
 return(0);
}

/*
 * freturn either an 'O' for open or a 'C' for close else return space
 */
extern char __cod_opcl_ch(char *codstr)
{
 char *chp;
 for (chp = codstr; *chp != '\0'; chp++)
  {
   /* SJM 01-21-22 - can a code have both O and C? - think not */
   if (*chp == 'O') return('O');
   if (*chp == 'C') return('C');
  }
 return(' ');
}

/*
 * TRAN TAB SORT COMPARE ROUTINES
 */

/*
 * compare 2 trans for qsort -  -1 first
 * 1) group by underlying symbol, then full sym within then date within
 * - transactions with O code (oepn) come before any with C (close)
 * - do not group by transaction type or instrument type
 */
extern int32 __cmp_trans_rec(const void *p1, const void *p2)
{
 int32 cv;
 char *chp, *chp2, p1ch, p2ch;
 struct trans_t *trp1, *trp2;

  trp1 = (*((struct trans_t **) p1)); 
  trp2 = (*((struct trans_t **) p2)); 
  /* various debit/credit trans type records go at end */
  /* SJM 02-07-22 - divs now go at end sorted by date */
  if (is_debcred_trans(trp1) && !is_debcred_trans(trp2)) return(1);
  if (!is_debcred_trans(trp1) && is_debcred_trans(trp2)) return(-1);

  if (is_debcred_trans(trp1) && is_debcred_trans(trp2))
   {
    /* both debit/credit type trans - sort by date */
    cv = __cmp_dt(&(trp1->tdate), &(trp2->tdate));
    return(cv);
   }

 cv = strcmp(trp1->symbol, trp2->symbol);
 if (cv != 0) return(cv);
 /* know underlying symbol same */
 if (strcmp(trp1->sort_sym, "") != 0 || strcmp(trp2->sort_sym, "") != 0)
  {
   /* if both do not have full sym, non full sym last */
   if (strcmp(trp1->sort_sym, "") == 0) return(1);
   if (strcmp(trp2->sort_sym, "") == 0) return(-1);
   /* SJM 01-30-22 - if want to see normal sym, can fix on output */
   cv = strcmp(trp1->sort_sym, trp2->sort_sym);
   if (cv != 0) return(cv);
   /* SJM 02-09-25 will fall thru now if same */
  }
 /* 2 ways to get here both full syms empty or both the same */
 /* SJM 02-09-25 for op pos records just need to group */ 
  cv = __cmp_dt(&(trp1->tdate), &(trp2->tdate));
  /* want latest (newest) date to come first not last */
  /* SJM 02-09-25 - want newest first was backward */
  // cmp dt wrong - do not want this: cv = -cv;
  if (cv != 0) return(cv); 
  /* SJM 04-21-25 - because of sep reverses time only diff */
  cv = __cmp_clktim(&(trp1->ttime), &(trp2->ttime));
  if (cv != 0) return(cv); 

  /* open-close ch is one of O, C or space for something else */
  chp = (char *) &(trp1->tr_code);
  p1ch = __cod_opcl_ch(chp);
  chp2 = (char *) &(trp2->tr_code);
  p2ch = __cod_opcl_ch(chp2);
  if (p1ch == 'O' && p2ch == 'O') return(0);
  if (p1ch == 'C' && p2ch == 'C') return(0);
  if (p1ch == ' ' && p2ch == ' ') return(0);
  if (p1ch == 'O' && p2ch == 'C') return(-1);
  if (p1ch == 'C' && p2ch == 'O') return(1);
  if ((p1ch == 'O' || p1ch == 'C') && p2ch == ' ') return(-1);
  if ((p2ch == 'O' || p2ch == 'C') && p1ch == ' ') return(1);
  /* since all cases considered - should not get here */
  __misc_terr(__FILE__, __LINE__);
 return(-1);
}

/*
 * return T if trans record is deb/credit type
 * SJM 10-22-25 - FIXME BEWARE - if you add a new trans type,
 * this must be updated or symndx will not be built right
 * SJM 10-22-25 - think everything but TRADE goes here
 */
static int32 is_debcred_trans(struct trans_t *trp)
{
 if (trp->tr_typ == TRTYP_OPPOS
  || trp->tr_typ == TRTYP_DIV
  || trp->tr_typ == TRTYP_INLIEU_DIV
  || trp->tr_typ == TRTYP_DIVACRU 
  || trp->tr_typ == TRTYP_FEE
  || trp->tr_typ == TRTYP_TAX
  || trp->tr_typ == TRTYP_DEPOSIT
  || trp->tr_typ == TRTYP_WITHDRAW
  || trp->tr_typ == TRTYP_INTEREST
  || trp->tr_typ == TRTYP_BALANCE) return(TRUE);
 return(FALSE); 
}

/*
 * DATE/TIME ROUTINES
 */

/*
 * map 3 field (not jdt) date to a string
 *
 * dates are stored with month starting at 1, day starting at 1 and
 * year 4 digits - reader must be convert and correct if needed
 */
extern char *__dt_to_string(char *s, struct date_t *datp)
{
 sprintf(s, "%02d-%02d-%02d", datp->month, datp->day, datp->year);
 return(s);
}

/*
 * compare to 3 fiied dates just for equality
 *
 * notice order compare needs mapping to julian format
 */
extern int32 __dt_equals(struct date_t *datp1, struct date_t *datp2)
{
 if (datp1->year == datp2->year && datp1->month == datp2->month
  && datp1->day == datp2->day) return(TRUE);
 return(FALSE);
}

/*
 * compare two dat_t records
 */
extern int32 __cmp_dt(const struct date_t *d1p, const struct date_t *d2p)
{
 /* then date within symbol */
 if (d1p->year < d2p->year) return(-1);
 else if (d1p->year > d2p->year) return(1);
 /* if same year - compare m */
 if (d1p->month < d2p->month) return(-1);
 else if (d1p->month > d2p->month) return(1);
 /* finally by day */
 if (d1p->day < d2p->day) return(-1);
 else if (d1p->day > d2p->day) return(1);
 return(0);
}

/*
 * compare two clktime records - need for sep. where sec. inced
 * if dt equal usually because of sep of reverses, also cmp tim
 */
extern int32 __cmp_clktim(const struct clktime_t *t1p,
 const struct clktime_t *t2p)
{
 /* then time */
 if (t1p->hr < t2p->hr) return(-1);
 else if (t1p->hr > t2p->hr) return(1);
 /* if same hour - compare minute */
 if (t1p->min < t2p->min) return(-1);
 else if (t1p->min > t2p->min) return(1);
 /* finally by sec */
 if (t1p->sec < t2p->sec) return(-1);
 else if (t1p->sec > t2p->sec) return(1);
 return(0);
}

/*
 * JULIAN DATE/TIME ROUTINES
 */

/*
 * convert a date to a julian date 0 is (1 ad?)
 * julian date means number of days 1 ad
 * wrong results if called with illegal date
 * day and month start from 1
 */
extern int32 __to_julian(struct date_t *datp)
{
 int32 mtmp, dtmp, ytmp, ctmp;
 int32 t1, t2, t3;

 if (datp->month > 2) { mtmp = datp->month - 3; ytmp = datp->year; }
 else { mtmp = datp->month + 9; ytmp = datp->year - 1; }
 ctmp = ytmp/100;
 dtmp = ytmp - 100*ctmp;
 t1 = 146097L*ctmp/4;
 t2 = (1461*dtmp)/4;
 t3 = (153*mtmp + 2)/5;

 /* DBG remove ---
 __msg("date = %02d/%02d/%04d (%d)\n", datp->month, datp->day, datp->year,
  t1 + datp->day + t2 +17211119L + t3);
 --- */

 return(t1 + datp->day + t2 + 1721119L + t3);
}

/*
 * return true if this is a leapyear
 */
extern int32 __leapyear(int32 y)
{
 int32 retval = FALSE;

 /* divisible by 4 but but not 100 except a leap year every fourth century */
 if (y == (4*(y/4))) retval = TRUE;
 if (y == (100*(y/100))) retval = FALSE;
 if (y == (400*(y/400))) retval = TRUE;
 return(retval);
}

/*
 * convert a julian date to dy, mm, and yr values (all start from 1)
 * always possible but may be nonsense
 */
extern void _from_julian(int32 *dy, int32 *mth, int32 *yr, int32 jno)
{
 int32 y, m, d, mtmp;

 mtmp = jno - 1721119L;
 y = (4*mtmp - 1)/146097L;
 mtmp = 4*mtmp - 1 - 146097L*y;
 d = mtmp/4;
 mtmp = (4*d + 3)/1461;
 d = 4*d + 3 - 1461*mtmp;
 d = (d + 4)/4;
 m = (5*d - 3)/153;
 d = 5*d - 3 - 153*m;
 d = (d + 5)/5;
 y = 100*y + mtmp;
 if (m < 10) m += 3; else { m -= 9; y++; }
 *dy = d;
 *mth = m;
 *yr = y;
 /* DBG remove ---
 __msg("jdt=%d (%02d/%02d/%04d)\n", jno, *mth, *dy, *yr);
 --- */
}

/*
 * convert numeric date to day in week (0 is sunday through 6 is saturday) 
 * expects 4 digit year
 */
extern int32 __date_to_wkday(int32 m, int32 d, int32 y)
{
 int32 ctmp, dtmp, mtmp, ytmp, n;

 if (m > 2) { mtmp = m - 2; ytmp = y; } else { mtmp = m + 10; ytmp = y - 1; }
 ctmp = ytmp/100;
 dtmp = ytmp - 100*ctmp;
 n = ((13*mtmp -1)/5)+d + dtmp + (dtmp/4)+(ctmp/4) - ctmp - ctmp + 77;
 return(n - 7*(n/7));
}

static char *day_name[] = { "sunday", "monday", "tuesday", "wednesday",
  "thursday", "friday", "saturday" };

/*
 * return name of day - assumes index legal
 */
extern char *__to_dayname(int32 di)
{
 return(day_name[di]);
}

static char *mth_abbrev[] = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
  "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };

static char *mth_name[] = { "January", "February", "March", "April",
  "May", "June", "July", "August", "September", "October", "November",
  "December" };

/*
 * return name of month - assumes index legal and starts with jan 1
 */
extern char *__mth_num_to_name(char *s, int32 di)
{
 strcpy(s, mth_name[di - 1]);
 return(s);
}

extern char *__mth_num_to_mthabrv(char *s, int32 di)
{
 strcpy(s, mth_abbrev[di - 1]);
 return(s);
}

/*
 * IB spelled out month name to mth num starting at 1 - returns -1 on error
 * because that is IB world storage method
 */
extern int32 __mth_nam_to_num(char *s)
{
 int32 i;

 for (i = 0; i < 12; i++)
  {
   if (strcmp(mth_name[i], s) == 0) return(i + 1);
  }
 return(-1);
}

/*
 * IB spelled out month abbrev to mth num starting at 0 - returns -1 on error
 * jan is 1`0j
 */
extern int32 __month_abbrev_to_num(char *s)
{
 int32 i;

 for (i = 0; i < 12; i++)
  {
   if (strcmp(mth_abbrev[i], s) == 0) return(i + 1);
  }
 return(-1);
}

static char *mth_let[] = { "F", "G", "H", "J", "K", "M", "N", "Q", "U", "V",
  "X", "Z" };

/*
 * month number (1 is jan) to future convention month letter
 */
extern char * __mth_num_to_fut_let(char *s, int32 mnum)
{
 strcpy(s, mth_let[mnum - 1]);
 return(s);
}

/*
 * in src future abbrev letter to month nmuber
 */
extern int32 __month_let_to_num(char futabrev_let)
{
 int32 i;
 char s1[RECLEN];

 s1[0] = futabrev_let;
 s1[1] = '\0';
 for (i = 0; i < 12; i++)
  {
   if (strcmp(mth_let[i], s1) == 0)
    return(i + 1);
  }
 return(-1);
}

/*
 * fill both jdt and date struct records for today
 */
extern void __set_today_dates(void)
{
 time_t now;
 struct tm *tmvalp;
 
 if (time(&now) == (time_t) -1) __misc_terr(__FILE__, __LINE__);   
 tmvalp = localtime(&now); 
 __today_dt.month = tmvalp->tm_mon + 1;
 __today_dt.day = tmvalp->tm_mday;
 /* year starts at 1900 but date_t store as yy (offset from 2000) */
 __today_dt.year = tmvalp->tm_year - 100;

 __today_jdt = __to_julian(&(__today_dt));
}

/*
 * ROUTINES TO CONVERT DATE VARIOUS FORMATS to MONTH DAY YEAR
 */

/*
 * convert date and time string into data and time records
 * this IB format "2020-09-22, 15:21:46 EDT"
 *
 * mostly for trade date/time
 */
extern int32 __xtrct_ib_csv_dattim(char *dattimstr, struct date_t *datp,
 struct clktime_t *clktimp)
{
 char *chp, *chp2;
 char s1[RECLEN], s2[RECLEN];

 if ((chp = strchr(dattimstr, ',')) == NULL)
  {
bad_dattim:
   __misc_terr(__FILE__, __LINE__);
   exit(5);
  }
 strncpy(s1, dattimstr, chp - dattimstr + 1);
 s1[chp - dattimstr] = '\0';
 if (__dash_num_date_to_datrec(s1, datp) == -1) goto bad_dattim;
 chp++; chp++;
 /* SJM 12-29-20 - time zone only in statement section date-time */
 /* not in trade time stamp */
 strcpy(s2, chp);
 if ((chp2 = strchr(s2, ' ')) != NULL)
  {
   /* can change local s2 */
   *chp2 = '\0';
   strcpy(s2, chp);
  }
 if (__ib_time_to_clktim(s2, clktimp) == -1) goto bad_dattim;
 return(0);
}

/*
 * convert <month name> <day number>, <year> to date rec
 * returns -1 on error - 0 on good
 *
 * Statement,Data,Period,"January 2, 2019 - November 22, 2019"
 */
extern int32 __wrd_date_rng_str_to_date(char *datstr, struct date_t *datfrp,
 struct date_t *dattop)
{
 int32 day1, day2, yr1, yr2, mth1, mth2;
 char *chp, *chp2;
 char mth1str[RECLEN], mth2str[RECLEN]; 

 __init_dat(datfrp);
 __init_dat(dattop);
 chp  = &(mth1str[0]);
 if (sscanf(datstr, "%s %d, %d", chp, &(day1), &(yr1)) != 3)
  return(-1);
 if ((mth1 = __mth_nam_to_num(mth1str)) == -1) return(-1);
 datfrp->month = mth1;
 datfrp->day = day1;
 datfrp->year = yr1;

 if ((chp = strchr(datstr, '-')) == NULL)
  {
   /* for one day file no to date so make them the same */
   *dattop = *datfrp; 
   return(0);
  }
 chp++; chp++;
 chp2 = mth2str;
 if (sscanf(chp, "%s %d, %d", chp2, &(day2), &(yr2)) != 3)
  return(-1);

 if ((mth2 = __mth_nam_to_num(mth2str)) == -1) return(-1);
 dattop->month = mth2;
 dattop->day = day2;
 dattop->year = yr2;
 return(0);
}

/*
 * convert dash date <year>-<month><day> to date rec
 *
 * Statement,Data,WhenGenerated,"2019-11-24, 18:17:21 EST"
 */
extern int32 __dash_num_date_to_datrec(char *datnumstr, struct date_t *datp)
{
 int32 yr1, mth1, day1;

 __init_dat(datp);
 if (sscanf(datnumstr, "%d-%d-%d", &(yr1), &(mth1), &(day1)) != 3)
  return(-1);
 datp->month = mth1;
 datp->day = day1;
 datp->year = yr1;
 return(0); 
}

/*
 * convert <hr>:<min>:<sec> to clktime_t
 *
 * Statement,Data,WhenGenerated,"2019-11-24, 18:17:21 EST"
 */
extern int32 __ib_time_to_clktim(char *timstr, struct clktime_t *clktimp)
{
 int32 hour, minute, second;

 __init_clktim(clktimp);
 if (sscanf(timstr, "%d:%d:%d", &(hour), &(minute), &(second)) != 3)
  return(-1);
 clktimp->hr = hour;
 clktimp->min = minute;
 clktimp->sec = second;
 return(0); 
}

/*
 * initiailize a date record
 */
extern void __init_dat(struct date_t *datp)
{
 datp->month = 1;
 datp->day = 1;
 datp->year = 00;
}

/*
 * initiailize a clcok time record
 */
extern void __init_clktim(struct clktime_t *clktimp)
{
 clktimp->hr = 0;
 clktimp->min= 0;
 clktimp->sec = 0;
}

/*
 * ROUTINES TO TAKE APART OPTION DESCRIPTION STRINGS
 */

/*
 * extract equity or index stock type option properties
 * returns -1 on error else 0
 *
 * caller must pass address for this routine to fill value into
 * take apart: WYNN  200403P00080000
 */
extern int32 __xtrct_ib_option_desc(char *s, char *sym, struct date_t *expdt,
 int32 *is_put, double *strike)
{
 int32 yr, dy;
 char s1[RECLEN], *chp;

 if ((chp = strchr(s, ' ')) == NULL) return(-1);
 strncpy(sym, s, chp - s);
 sym[chp - s] = '\0'; 
 /* will be right padding spaces to max option symbol len */
 while (*chp == ' ') chp++;
 /* option expiration date is ddmmmyy - where mmm is 3 letter mth abbrev */
 strncpy(s1, chp, 2);
 s1[2] = '\0';
 if (sscanf(s1, "%d", &dy) != 1) return(-1);
 expdt->day = dy;

 strncpy(s1, &(chp[2]), 3);
 s1[3] = '\0';
 expdt->month = __month_abbrev_to_num(s1);

 /* SJM 10-12-20 - FIXME - is that a need to deal with 1900 expirations? */  
 strncpy(s1, &(chp[5]), 2);
 s1[3] = '\0';
 if (sscanf(s1, "%d", &yr) != 1) return(-1);
 expdt->year = yr; 

 chp = &(chp[8]);
 while (*chp == '0') chp++;
 if (*chp == '\0') return(-1);
 if (!isdigit(*chp)) return(-1);
 /* SJM 10-12-20 - FIXME - closing price determines where to put decimal? */
 if (sscanf(chp, "%lf", strike) != 1) return(-1);
 /* find the space after strike */
 while (*chp != ' ') chp++;
 chp++;
 if (*chp == 'P') *is_put = TRUE;
 else if (*chp == 'C') *is_put = FALSE;
 else return(-1);
 return(0);
}

/*
 * for IN extract futures option properties
 *
 * take apart: ES 14FEB20 3275.0 P 
 * SJM 08-10-22 might also be "ZS OCT22 13 P" - expires 22 sep?
 */
extern int32 __xtrct_ib_futopt_desc(char *s, char *sym, struct date_t *expdt,
 int32 *is_put, double *strike)
{
 int32 yr, dy;
 char s1[RECLEN], *chp, *chp2;
 
 if ((chp = strchr(s, ' ')) == NULL) return(-1);
 strncpy(sym, s, chp - s); 
 sym[chp - s] = '\0';
 chp++;
 if (isdigit(*chp))
  {
   strncpy(s1, chp, 2);
   s1[2] = '\0';
   if (sscanf(s1, "%d", &(dy)) != 1) return(-1);
   expdt->day = dy;
   chp = &(chp[2]);
  }
 else expdt->day = 1;

 strncpy(s1, chp, 3);
 s1[3] = '\0';
 expdt->month = __month_abbrev_to_num(s1);
 chp = &(chp[3]);
 strncpy(s1, chp, 2);
 s1[2] = '\0';
 if (sscanf(s1, "%d", &(yr)) != 1) return(-1);
 expdt->year = yr;
 chp++; chp++; chp++; 
 if (*chp == ' ') chp++;
 if (!isdigit(*chp)) return(-1);
 if (sscanf(chp, "%lf", strike) != 1) return(-1);
 if (*strike < 0.0) return(-1);
 if ((chp2 = strchr(chp, ' ')) == NULL) return(-1); 
 chp2++;
 if (*chp2 == 'P') *is_put = TRUE; 
 else if (*chp2 == 'C') *is_put = FALSE;
 else return(-1);
 chp2++;
 if (*chp2 != '\0') return(-1);
 return(0);
}

/*
 * for Schwab extract equity  r index stock type option properties
 * returns -1 on error else 0
 *
 * caller must pass address for this routine to fill value into
 * take apart: "VTNR 07/15/22 12.50 P"
 * csv fld tab has always present surrounding double quotes removed
 */
extern int32 __xtrct_schwb_option_desc(char *s, char *sym,
 struct date_t *expdt, int32 *is_put, double *strike)
{
 int32 mth, dy, yr;
 char s1[RECLEN], *chp, *chp2;

 if ((chp = strchr(s, ' ')) == NULL) return(-1);
 strncpy(sym, s, chp - s);
 sym[chp - s] = '\0'; 
 chp++;
 if ((chp2 = strchr(chp, ' ')) == NULL) return(-1);
 strncpy(s1, chp, chp2 - chp);
 s1[chp2 - chp] = '\0';
 if (sscanf(s1, "%02d/%02d/%04d", &mth, &dy, &yr) != 3) return(-1);
 expdt->month = mth;
 expdt->day = dy;
 expdt->year = yr;

  chp2++;
 if ((chp = strchr(chp2, ' ')) == NULL) return(-1);
 strncpy(s1, chp2, chp - chp2);
 s1[chp - chp2] = '\0';
 if (sscanf(s1, "%lf", strike) != 1) return(-1);
 chp++;
 if (*chp != 'P' && *chp != 'C') return(-1);
 if (*chp == 'P') *is_put = TRUE;
 else if (*chp == 'C') *is_put = FALSE;
 else return(-1);
 return(0);
}

/*
 * MEMORY ALLOCATION INTERFACE ROUTINES
 */

/*
 * allocate a string from a big block
 * should maybe have separate place for entire run and this module strings ??
 */
extern char *__my_stralloc(char *s)
{
 char *cp;

 cp = __my_malloc(strlen(s) + 1);
 strcpy(cp, s);
 return(cp);
}

/*
 * call to malloc that dies if no memory available
 * these are normal OS memory allocation with error terminaton
 */
extern char *__my_malloc(size_t size)
{
 char *cp;

 if ((cp = malloc((size_t) size)) == NULL)
  {
   __msg("**fatal err[1]: No more memory - allocated %ld bytes\n", __mem_use);
   exit(4);
  }
 __mem_use += size;
 /* DBG remove ---
 __msg(__my_malloc - allocated %d bytes\n", size);
 --- */
 return(cp);
}

/*
 * interface to system realloc()
 * can only call with malloced mp or cannot realloc
 */
extern char *__my_realloc(char *mp, int32 osize, int32 nsize)
{
 char *cp;

 if ((cp = realloc(mp, (size_t) nsize)) == NULL)
  {
   __msg("**fatal err[1]: realloc failed - allocated %ld bytes\n", __mem_use);
   exit(4);
  }
 /* DBG remove ---
 __msg("realloc: freeing %d byte and allocating %d bytes\n", osize, nsize);
 --- */
 __mem_use -= osize;
 __mem_use += nsize;
 return(cp);
}

/*
 * call to free 
 */
extern void __my_free(char *mp, int32 size)
{
 if (size == 0) return;
 free(mp);
 /* if (__debug_flg) __msg("freeing %d bytes\n", size); */
 __mem_use -= size;
}

/*
 * PRINT AND ERROR MESSAGE ROUTINES 
 */

/*
 * print a k character long blank string
 *
 * FIXME - should use one printf of substring of work blank string
 */
extern void __prt_blanks(FILE *f, int32 k)
{
 register int32 i; 

 for (i = 0; i < k; i++) fprintf(f, " ");
}

/*
 * miscellaneous (most) internal fatal errors 
 */
extern void __misc_terr(char *fnam, int32 lno)
{
 __terr(101, "MISC INTERNAL ERROR** - source line **%s(%d)", fnam, lno);
}

/*
 * simple case fatal error - can always find with debugger
 */
extern void __case_terr(char *fnam, int32 lno)
{
 __terr(102, "CASE INTERNAL ERROR** - source line **%s(%d)", fnam, lno);
}

/*
 * simple argument passed to routine internal error
 */
extern void __arg_terr(char *fnam, int32 lno)
{
 __terr(103, "ARG INTERNAL ERROR** - source line **%s(%d)", fnam, lno);
}

/*
 * error with fatal termination
 * arg s is a format specification string for sprintf
 */
extern void __terr(int32 id_num, char *s, ...)
{
 va_list va, va2;

 __msg("**FATAL ERROR** %s [%d] ", __brkrnam, id_num);

 va_start(va, s);
 va_start(va2, s);
 vfprintf(stdout, s, va);
 if (__log_s != NULL) vfprintf(__log_s, s, va2);
 va_end(va2);
 va_end(va);

 __msg("\n");
 exit(1);
}

/*
 * serious but non-fatal error
 */
extern void __err(int32 id_num, char *s, ...)
{
 va_list va, va2;

 __msg("** %s ERROR** [%d] ", __brkrnam, id_num);

 va_start(va, s);
 va_start(va2, s);
 vfprintf(stdout, s, va);
 if (__log_s != NULL) vfprintf(__log_s, s, va2);
 va_end(va2);
 va_end(va);

 __msg("\n");
 __error_cnt++;
}

/*
 * serious but non-fatal error while reading IB .csv file
 * uses globals __line_cnt and __curfnam
 */
extern void __ferr(int32 id_num, char *s, ...)
{
 va_list va, va2;

 __msg("**%s(%d) %s ERROR** [%d] ", __curfnam, __line_cnt, __brkrnam, id_num);

 va_start(va, s);
 va_start(va2, s);
 vfprintf(stdout, s, va);
 if (__log_s != NULL) vfprintf(__log_s, s, va2);
 va_end(va2);
 va_end(va);

 __msg("\n");
 __error_cnt++;
}

/*
 * warn
 */
extern void __warn(int32 id_num, char *s, ...)
{
 va_list va, va2;

 __msg("**%s WARN** [%d] ", __brkrnam, id_num);

 va_start(va, s);
 va_start(va2, s);
 vfprintf(stdout, s, va);
 if (__log_s != NULL) vfprintf(__log_s, s, va2);
 va_end(va2);
 va_end(va);

 __msg("\n");
 __warn_cnt++;
}

/*
 * serious but non-fatal error while reading IB .csv file
 * uses globals __line_cnt and __curfnam
 */
extern void __fwarn(int32 id_num, char *s, ...)
{
 va_list va, va2;

 __msg("**%s(%d) %s ERROR** [%d] ", __curfnam, __line_cnt, __brkrnam, id_num);

 va_start(va, s);
 va_start(va2, s);
 vfprintf(stdout, s, va);
 if (__log_s != NULL) vfprintf(__log_s, s, va2);
 va_end(va2);
 va_end(va);

 __msg("\n");
 __warn_cnt++;
}

extern void __inform(int32 id_num, char *s, ...)
{
 va_list va, va2;

 __msg("--%s INFORM: [%d] ", __brkrnam, id_num);

 va_start(va, s);
 va_start(va2, s);
 vfprintf(stdout, s, va);
 if (__log_s != NULL) vfprintf(__log_s, s, va2);
 va_end(va2);
 va_end(va);
 __msg("\n");
 __inform_cnt++;
}

/*
 * my fprintf - normal fprintf except if f is stdout then also to log file
 */
extern void __msg(char *s, ...)
{
 va_list va, va2;

 va_start(va, s);
 va_start(va2, s);
 vfprintf(stdout, s, va);
 if (__log_s != NULL) vfprintf(__log_s, s, va2);
 va_end(va2);
 va_end(va);
}

/*
 * version of my fprintf when caller passed variable arg list 
 */
extern void __my_vfprintf(FILE *f, char *s, va_list args)
{
 vfprintf(f, s, args);
}
