/*
  sim.c
  (C) Copyright 2008 Eric Shalov. All Rights Reserved.
  
  Simulate trading a futures contract (like 6E) on Chicago's CME GLOBEX exchange
  for back-testing purposes.

  This simulation determines the correct times during a trading day to enter
  a futures contract position using a Stop-Stop-Limit Order, an order to Buy
  (or Sell) futures contracts that will automatically exit the position when
  a certain number of profit points have been gained, or when trading goes
  against us, and an unacceptable maximum number of points have been lost.
  
  The correct moment to enter a position is based on when trading on the
  market has led to a price that is approaching a pivot point, but is at a
  specific safe distance (offset) from it. The other prerequisites to
  entering a position include that trading has travelled a minimum distance
  from a recent low (or high) price, enough of a movement suffiently
  significant to indicate a trend.
  
  When the program begins, technical pivot points are determined based on
  standard formulas that calculate them using the prior trading day's high,
  low, and closing prices.
  
  The calculations used are taken from Wikipedia:
    http://en.wikipedia.org/wiki/Pivot_point_calculations

  The program then proceeds to analyze every trade of a specified
  trading-day chronologically, simulating when positions are entered and
  when they are exited. For simplicity, the program assumes that desired
  orders are always filled at the current trading price, and that positions
  are always successfully closed at one of the specified stop-gain or
  stop-loss order prices.
  
  Each "roundturn" (the two trades used to enter and exit a position) is
  reported, along with a summary total of number of points gained/lossed
  from the day's trading.
  
  The logic is:
        BUY if
        price = RESISTANCE-6 AND (price-20min LOW) >= 10 AND last_line_crossed=RESISTANCE
              
          OR
        
        SELL if
        price = SUPPORT+6 AND (price+20 MINUTE HIGH) >= 10 AND last_line_crossed=SUPPORT

  The summary conclusion, after testing with numerous alternative sets of
  pivot points, time windows, and entry/exit rules, tested over numerous
  different trading-days, is that the end gain/loss result will always
  statistically approximate zero, as it would if the pivot points had been
  selected at random. This suggests that, at a minimum, the traditional
  pivot point calculations do not indicate the existance of any real market
  phenomena.

  (On a technical note, we must use "long doubles" (on Intel, at least),
  because the internal representation of floats will cause 0.1 + 0.2 != 0.3.)
 
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <unistd.h>

char *data_filename = "723.dat";
long double prev_open= 1.5744, prev_high = 1.5754, prev_low= 1.5626, prev_close = 1.5646; /*  7/23/08 */
int seconds_lookback    = 20*60; /* 20 minutes */
int use_extended_pivots = 0;
long double win_difference    = 0.0003;
long double lose_difference   = 0.0007;
long double resistance_offset = 0.0006; /* this must be positive */
long double support_offset    = 0.0006; /* this must be positive */
long double trend_movement    = 0.0010;
int debug = 0;

/**********************************************************************************/

struct trade_data {
  long date,time;
  long double price;
  long size;
  time_t unixtime;
};

long num_trades;
struct trade_data *trade = NULL;

struct pivot_data {
  long double value;
  char name[10];
};

struct pivot_data pivot[1024];
int num_pivots = 0;

/* Score-keeping */
unsigned long
  num_roundturns = 0,
  num_long_roundturns = 0,
  num_short_roundturns = 0;
unsigned long
  num_profitable_roundturns = 0,
  num_unprofitable_roundturns = 0;
long double total_difference = 0.0;

/* prototypes */
void add_pivot(char *name, long double n);
int pivot_name_comp(const void *a, const void *b);
int pivot_value_comp(const void *a, const void *b);
void add_pivots(long double prev_open,long double prev_high,long double prev_low,long double prev_close);
void record_roundturn(long double entry_price,long double exit_price,time_t entry_unixtime,time_t exit_unixtime,int position);

/************************************************************************************/

int main(int argc, char *argv[]) {
  FILE *f;
  long date,time;
  long double price;
  long size;
  int i;
  int resistance=-1, support=-1, last_resistance=-1, last_support=-1, last_pivot_crossed = -1, on_pivot;
  long pivots_crossed=0;
  struct tm tm;
  time_t current_unixtime;
  int v;
  long double recent_high, recent_low;
  char opt;
  int opt_index;

  /* process arguments */
  while ((opt = getopt (argc, argv, "def:o:h:l:c:s:W:L:R:S:T:")) != -1)
    switch(opt) {
      case 'd':
        ++debug;
        printf("Debug level set to %d\n", debug);
        break;
      case 'e':
        use_extended_pivots = 1;
        printf("Extended Pivots enabled.\n");
        break;
      case 'f':
        data_filename = optarg;
        printf("Reading data from `%s'.\n", data_filename);
        break;
      case 'o':
        prev_open = (long double)atof(optarg);
        printf("Using previous day open: %Lf\n", prev_open);
        break;
      case 'h':
        prev_high = (long double)atof(optarg);
        printf("Using previous day high: %Lf\n", prev_high);
        break;
      case 'l':
        prev_low = (long double)atof(optarg);
        printf("Using previous day low: %Lf\n", prev_low);
        break;
      case 'c':
        prev_close = (long double)atof(optarg);
        printf("Using previous day close: %Lf\n", prev_close);
        break;
      case 's':
        seconds_lookback = atoi(optarg);
        printf("Time window set to %d seconds.\n", seconds_lookback);
        break;
      case 'W':
        win_difference = (long double)atof(optarg);
        printf("Stop-profit set at %Lf from position-entrance.\n", win_difference);
        break;
      case 'L':
        lose_difference = (long double)atof(optarg);
        printf("Stop-loss set at %Lf from position-entrance.\n", lose_difference);
        break;
      case 'R':
        resistance_offset = (long double)atof(optarg);
        printf("Entrance price offset set at %Lf below resistance pivot.\n", resistance_offset);
        break;
      case 'S':
        support_offset = (long double)atof(optarg);
        printf("Entrance price offset set at %Lf above support pivot.\n", support_offset);
        break;
      case 'T':
        trend_movement = (long double)atof(optarg);
        printf("Minimum movement to qualify as trend set at %Lf.\n", trend_movement);
        break;
      default:
        fprintf(stderr,"Usage: %s [-de] [-f filename] [-o open] [-h high] [-l low] [-c close]\n"
        "\t[-s seconds] [-W win] [-L lose] [-R resist_offset] [-S support_offset] [-T trend]\n", argv[0]);
        exit(1);
    }
  
  for (opt_index = optind; opt_index < argc; opt_index++)
        printf ("Non-option argument %s\n", argv[opt_index]);
  
  if(argc == 6) {
    data_filename = argv[1];
    prev_open =  (long double)atof(argv[2]);
    prev_high =  (long double)atof(argv[3]);
    prev_low =   (long double)atof(argv[4]);
    prev_close = (long double)atof(argv[5]);
  }

  /* For position-tracking */
#define POSITION_CLOSED 1 /* not in a position */
#define POSITION_LONG   2 /* we have bought, need to sell to close position */
#define POSITION_SHORT  3 /* we have sold, need to buy to close position */
  int position = POSITION_CLOSED;
  
  long double resistance_stop_price=-0.1, support_stop_price=-0.1;
  time_t entry_unixtime;
  long double entry_price = -1.0;


  /* calculate pivots using previous day's open, high, low, close */
  add_pivots(prev_open, prev_high, prev_low, prev_close);
  
  if( (f=fopen(data_filename,"r")) ) {
    while( fscanf(f, "%ld %ld;%Lf;%ld\n", &date, &time, &price, &size) != -1) {
      if(debug) printf("[%06ld] %08ld / %06ld %6.4Lf %ld\n", num_trades+1, date, time, price, size);
      
      ++num_trades;
      
      /* save each trade in a growing dynamic memory array */
      if(num_trades == 0) {
        trade = malloc( sizeof(struct trade_data) );
      } else {
        trade = realloc(trade, num_trades * sizeof(struct trade_data) );
        if(trade == NULL) {
          fprintf(stderr,"realloc() failed!\n");
          exit(1);
        }
      }

      trade[num_trades-1].date = date;
      trade[num_trades-1].time = time;
      trade[num_trades-1].price = price;
      trade[num_trades-1].size = size;

      /* convert date+time into UNIX time */
      tm.tm_sec = trade[num_trades-1].time % 100;
      tm.tm_min = (trade[num_trades-1].time / 100) % 100;
      tm.tm_hour = (trade[num_trades-1].time / 10000);
      tm.tm_mday = trade[num_trades-1].date % 100;
      tm.tm_mon = ((trade[num_trades-1].date % 10000) / 100) - 1; /* months since January [0-11] */
      tm.tm_year = (trade[num_trades-1].date / 10000) - 1900; /* years since 1900 */
      tm.tm_zone = NULL;
      trade[num_trades-1].unixtime = current_unixtime = mktime(&tm);

      /* Determine "resistance" and "support" pivot points based on current trade price */      
      last_resistance = resistance;
      last_support = support;
      on_pivot = 0;
      
      /* Check if we're right on a pivot point */
      for(i=0;i<num_pivots;i++) {
        if(price == pivot[i].value) {
          if(debug) printf("Price is exactly at pivot %s\n", pivot[i].name);
          on_pivot = 1;
        }
      }

      if(!on_pivot) {
        if((resistance == -1) ||(resistance != support)) {
          for(i=0;i<num_pivots;i++) {
            if(pivot[i].value > price) {
              support = i-1;
              resistance = i;
              break;
            }
          }
        }
      }
      
      if(debug) printf("resistance = %d(%6.4Lf), support = %d(%6.4Lf), last_resistance = %d, last_support = %d\n",
        resistance, pivot[resistance].value, support, pivot[support].value, last_resistance, last_support);

      /* have we crossed any lines?
         If this trade puts us above our previous "resistance" pivot line or below our
         previous "support" pivot line, then we've crossed a line.
      */
      if(debug) printf("resistance = %d, support = %d, last_resistance = %d, last_support = %d\n",
        resistance, support, last_resistance, last_support);

      if( ((resistance != last_resistance) || (support != last_support)) && last_resistance != -1 ) {
        ++pivots_crossed;

        if(price == pivot[last_resistance].value || price == pivot[last_support].value) {
          printf("Market exactly at a pivot.\n");
        } else {
          if(price > pivot[last_resistance].value) {
            if(debug) printf("%4ld: [%06ld] %08ld %06ld Market has risen (%6.4Lf) above the resistance pivot (%6.4Lf)\n",
              pivots_crossed, num_trades, date, time, price, pivot[last_resistance].value);
            last_pivot_crossed = last_resistance;
          }

          if(price < pivot[last_support].value) {
            if(debug) printf("%4ld: [%06ld] %08ld %06ld Market has fallen (%6.4Lf) below the support pivot (%6.4Lf)\n",
              pivots_crossed, num_trades, date, time, price, pivot[last_support].value);
            last_pivot_crossed = last_support;
          }

          if(debug) printf("      New RESISTANCE: %-6s(%6.4Lf),\n"
                           "          SUPPORT:    %-6s(%6.4Lf)   (gap=%.0f).\n\n",
            pivot[resistance].name, pivot[resistance].value, pivot[support].name, pivot[support].value,
            floorf((pivot[resistance].value - pivot[support].value)/0.0001));
        }
      }


      /* If we're in a position, check if the trading price has hit our exit price */
      if(position != POSITION_CLOSED) {
        if(price >= resistance_stop_price) {
            record_roundturn(entry_price,resistance_stop_price,entry_unixtime,trade[num_trades-1].unixtime, position);
            position = POSITION_CLOSED;
        }
        
        if(price <= support_stop_price) {
            record_roundturn(entry_price,support_stop_price,entry_unixtime,trade[num_trades-1].unixtime,position);
            position = POSITION_CLOSED;
        }
      }

  /* Determine if it's time to enter a trade */
  if(debug) printf("Current position: %s\n",
    position == POSITION_CLOSED  ? "CLOSED" :
    position == POSITION_LONG    ? "LONG" :
    position == POSITION_SHORT   ? "SHORT" :
    "??ERR??");
  if(position == POSITION_CLOSED)  /* can only trade if not in a position */
      if( (price == (pivot[resistance].value-resistance_offset)) ||
          (price == (pivot[support].value+support_offset)) ) {
          /* ok, now we scan through the last up-to 20 minutes of trades to determine
             the recent HIGH and LOW of the last 20 minutes */
          if(debug) printf("price is 6 centicents from pivot and last_pivot_crossed=%s\n",
            last_pivot_crossed==support ? "support" : last_pivot_crossed==resistance ? "resistance" : "??UNK??");

          /* Okay, scan through last 20 minutes of trades and
             determine "recent high" and "recent low" */
          recent_low = recent_high = trade[num_trades-1].price;
          for(v = num_trades-1; v >= 0; v--) {
            if(debug) printf("trade[%d] = %6.4Lf (now=%ld, trade=%ld ....  trade was %ld secs ago)\n",
              v,
              trade[v].price,
              current_unixtime,
              trade[v].unixtime,
              current_unixtime-trade[v].unixtime);
            if((current_unixtime - trade[v].unixtime) <= seconds_lookback) {
              /* check for highs and lows */
              if(trade[v].price > recent_high) recent_high = trade[v].price;
              if(trade[v].price < recent_low)  recent_low  = trade[v].price;
            }
          }
          
          if(debug) printf("Recent low = %6.4Lf, recent_high = %6.4Lf\n", recent_low, recent_high);

          if( (price == (pivot[resistance].value-resistance_offset)) &&
              (price-recent_low >= trend_movement) &&
              (last_pivot_crossed = support)  ) {
            if(debug) printf("ACTION: BUY\n");
            entry_unixtime = trade[num_trades-1].unixtime;
            resistance_stop_price = price + win_difference;
            support_stop_price = price - lose_difference;
            entry_price = price;
            position = POSITION_LONG;
          } else

          if( (price == (pivot[support].value+support_offset)) &&
              (recent_high-price >= trend_movement) &&
              (last_pivot_crossed = resistance)  ) {
            if(debug) printf("ACTION: SELL\n");
            entry_unixtime = trade[num_trades-1].unixtime;
            resistance_stop_price = price + lose_difference;
            support_stop_price = price - win_difference;
            entry_price = price;
            position = POSITION_SHORT;
          } else
          
          if(debug) printf("ACTION: NONE\n");
          
      }
    }
  }
  fclose(f);
  
  printf(
    "%6ld trades analyzed\n"
    "%6ld roundturns:\n"
    "%6ld   longs  (%.2f%%)\n"
    "%6ld   shorts (%.2f%%)\n"
    "%6ld   were profitable   (%.2f%%)\n"
    "%6ld   were unprofitable (%.2f%%)\n"
    "Sum of differences: %.4Lf\n",
    num_trades,
    num_roundturns,
    num_long_roundturns,         100.0*num_long_roundturns/num_roundturns,
    num_short_roundturns,        100.0*num_short_roundturns/num_roundturns,
    num_profitable_roundturns,   100.0*num_profitable_roundturns/num_roundturns,
    num_unprofitable_roundturns, 100.0*num_unprofitable_roundturns/num_roundturns,
    total_difference);
  return 0;
}


void add_pivot(char *name, long double n) {
  strcpy(pivot[num_pivots].name, name);
  pivot[num_pivots].value = floorf(n*10000.0)/10000.0;
  ++num_pivots;
}

int pivot_name_comp(const void *a, const void *b) {
  struct pivot_data *ap, *bp;
  ap = (struct pivot_data *)a;
  bp = (struct pivot_data *)b;
  return strcmp(ap->name, bp->name);
}

int pivot_value_comp(const void *a, const void *b) {
  struct pivot_data *ap, *bp;
  ap = (struct pivot_data *)a;
  bp = (struct pivot_data *)b;
  return ap->value == bp->value ? 0 : ap->value > bp->value ? 1 : -1;
}


void add_pivots(long double prev_open,long double prev_high,long double prev_low,long double prev_close) {
  int i;
  long double pp;

  pp = floorf(10000.0 * (prev_high+prev_low+prev_close) / 3.0) / 10000.0;

  add_pivot("PP", pp);
  add_pivot("S1", 2.0 * pp - prev_high);
  add_pivot("S2", pp - (prev_high - prev_low));
  add_pivot("S3", pp - (prev_high - prev_low) * 2.0);
  add_pivot("S4", pp - (prev_high - prev_low) * 3.0);
  add_pivot("R1", 2.0 * pp - prev_low);
  add_pivot("R2", pp + (prev_high - prev_low));
  add_pivot("R3", pp + (prev_high - prev_low) * 2.0);
  add_pivot("R4", pp + (prev_high - prev_low) * 3.0);

  /* "Resistance Cents" and "Support Cents" added as pivots */
  add_pivot("RC1", floorf(pp*100.0)/100.0+0.0100 );
  add_pivot("SC1", floorf(pp*100.0)/100.0 );
  add_pivot("RC2", floorf(pp*100.0)/100.0+0.0200 );
  add_pivot("SC2", floorf(pp*100.0)/100.0-0.0100 );

  /* Additional experimental pivot points added at +20 and -20 ticks above/below
     other pivot points */
  if(use_extended_pivots) {
    add_pivot("PP-20",  -0.0020+pp);
    add_pivot("S1-20",  -0.0020+2.0 * pp - prev_high);
    add_pivot("S2-20",  -0.0020+pp - (prev_high - prev_low));
    add_pivot("S3-20",  -0.0020+pp - (prev_high - prev_low) * 2.0);
    add_pivot("S4-20",  -0.0020+pp - (prev_high - prev_low) * 3.0);
    add_pivot("R1-20",  -0.0020+2.0 * pp - prev_low);
    add_pivot("R2-20",  -0.0020+pp + (prev_high - prev_low));
    add_pivot("R3-20",  -0.0020+pp + (prev_high - prev_low) * 2.0);
    add_pivot("R4-20",  -0.0020+pp + (prev_high - prev_low) * 3.0);
    add_pivot("RC1-20", -0.0020+floorf(pp*100.0)/100.0+0.0100);
    add_pivot("SC1-20", -0.0020+floorf(pp*100.0)/100.0);
    add_pivot("RC2-20", -0.0020+floorf(pp*100.0)/100.0+0.0200);
    add_pivot("SC2-20", -0.0020+floorf(pp*100.0)/100.0-0.0100);

    add_pivot("PP+20",  +0.0020+pp);
    add_pivot("S1+20",  +0.0020+2.0 * pp - prev_high);
    add_pivot("S2+20",  +0.0020+pp - (prev_high - prev_low));
    add_pivot("S3+20",  +0.0020+pp - (prev_high - prev_low) * 2.0);
    add_pivot("S4+20",  +0.0020+pp - (prev_high - prev_low) * 3.0);
    add_pivot("R1+20",  +0.0020+2.0 * pp - prev_low);
    add_pivot("R2+20",  +0.0020+pp + (prev_high - prev_low));
    add_pivot("R3+20",  +0.0020+pp + (prev_high - prev_low) * 2.0);
    add_pivot("R4+20",  +0.0020+pp + (prev_high - prev_low) * 3.0);
    add_pivot("RC1+20", +0.0020+floorf(pp*100.0)/100.0+0.0100);
    add_pivot("SC1+20", +0.0020+floorf(pp*100.0)/100.0);
    add_pivot("RC2+20", +0.0020+floorf(pp*100.0)/100.0+0.0200);
    add_pivot("SC2+20", +0.0020+floorf(pp*100.0)/100.0-0.0100);
  }

  qsort(pivot, num_pivots, sizeof(struct pivot_data), pivot_value_comp);
  
  for(i=0;i<num_pivots;i++) {
    printf("Pivot %2d: %-7s = %6.4Lf%c", i+1, pivot[i].name, pivot[i].value,
    ((i+1)%3)==0 ? '\n' : '\t');
  }
  printf("\n");
}

void record_roundturn(long double entry_price,long double exit_price,time_t entry_unixtime,time_t exit_unixtime,int position) {
  long double difference;
  struct tm entry_tm, exit_tm;

  ++num_roundturns;
  if(position == POSITION_LONG)  ++num_long_roundturns;
  if(position == POSITION_SHORT) ++num_short_roundturns;
  
  localtime_r(&entry_unixtime, &entry_tm);
  localtime_r(&exit_unixtime, &exit_tm);

  difference = (position == POSITION_LONG) ? (exit_price - entry_price) :
           (entry_price - exit_price);

  if(difference > 0.0) ++num_profitable_roundturns;
  if(difference < 0.0) ++num_unprofitable_roundturns;
  total_difference += difference;

  printf("GO %-5s at %6.4Lf (%02d:%02d:%02d), get out %4ld seconds later, at %6.4Lf (%02d:%02d:%02d)  (%s%6.4Lf)\n",
    (position == POSITION_LONG) ? "LONG" :
    (position == POSITION_SHORT) ? "SHORT" :
    "??ERR??",
    entry_price,
    entry_tm.tm_hour,entry_tm.tm_min,entry_tm.tm_sec,
    exit_unixtime - entry_unixtime,
    exit_price,
    exit_tm.tm_hour,exit_tm.tm_min,exit_tm.tm_sec,
    difference > 0.0 ? "+" : "",
    difference);
}

