/* strptime() */ #define _XOPEN_SOURCE #include #include #include #include #include #include /* for print_stats() only */ #define TCTS 5 #define TMES 0.0005 /* Stop Loss and Take Profit distances */ #define SLmin 0.1000 #define SLmax 0.1000 #define SLstep 0.0005 static double SL = SLmin; #define TPmin 0.1000 #define TPmax 0.1000 #define TPstep 0.0005 static double TP = TPmin; /* Bar-to-index threshold #1 */ #define Tmin 0.0002 #define Tmax 0.0019 #define Tstep 0.0003 static double T = Tmin; /* Bar-to-index threshold #2 */ #define T2min (T + T2step) #define T2max 0.0099 #define T2step 0.0010 static double T2 = Tmin + T2step; /* Position opening thresholds: * count of stats entries and mathematical expectation */ #define TCTOmin 3 #define TCTOmax 3 #define TCTOstep 1 static unsigned int TCTO = TCTOmin; #define TMEOmin 0.0003 #define TMEOmax 0.0100 #define TMEOstep 0.0005 static double TMEO = TMEOmin; /* Position closing thresholds */ #define TCTCmin 3 #define TCTCmax 3 #define TCTCstep 1 static unsigned int TCTC = TCTCmin; #define TMECmin -0.0047 #define TMECmax 0.0030 #define TMECstep 0.0005 static double TMEC = TMECmin; /* These settings are for micro accounts, e.g. at Alpari */ #define BALANCE 1000 #define LOT 1000 #define SPREAD 0.0002 #define LEVERAGE 100 /* During optimization, we might not be interested in parameters that result * in a loss of more than 20% of the initial deposit at any one time, hence * this unusually high stopout level */ #define STOPOUT (BALANCE * 0.8) typedef struct bar { struct bar *next; struct tm tm; double o, h, l, c; unsigned int v; } bar; typedef struct { bar *head, *tail; } bars; typedef unsigned int count; typedef struct { double u, d; count ct, cu, cd; } statent; #define N 5 static struct { statent cdh3[5][24][N][N][N]; statent cdh1[5][24][N]; statent ch2[24][N][N]; statent cd2[5][N][N]; statent c3[N][N][N]; } stats; typedef struct { enum { NONE, BUY, SELL } dir; double level, size; } position; static int parse_csv_line(const char *in, bar *out) { const char *p; int ni, nc; p = strptime(in, "%Y.%m.%d,%H:%M,", &out->tm); if (!p) return -1; nc = 0; ni = sscanf(p, "%lf,%lf,%lf,%lf,%u%n", &out->o, &out->h, &out->l, &out->c, &out->v, &nc); if ((ni != 5 && ni != 6) || (p[nc] != '\0' && p[nc] != '\n' && p[nc] != '\r')) return -1; return 0; } static void free_bars(bars *bars) { /* XXX */ } static bars *parse_csv_file(const char *filename) { FILE *f; char line[80]; bars *bars; bar *bar; int error; f = fopen(filename, "r"); if (!f) { perror("fopen"); return NULL; } bars = calloc(1, sizeof(*bars)); if (!bars) { errno = ENOMEM; perror("calloc"); fclose(f); return NULL; } error = 0; while (fgets(line, sizeof(line), f)) { bar = calloc(1, sizeof(*bar)); if (!bar) { errno = ENOMEM; perror("calloc"); error = 1; break; } if (parse_csv_line(line, bar)) { fprintf(stderr, "Failed to parse: %s", line); free(bar); bar = NULL; error = 1; break; } #if 0 printf("%d/%02d/%02d (%d) %d:%02d " "%.4f %.4f %.4f %.4f %u\n", 1900 + bar->tm.tm_year, bar->tm.tm_mon + 1, bar->tm.tm_mday, bar->tm.tm_wday, bar->tm.tm_hour, bar->tm.tm_min, bar->o, bar->h, bar->l, bar->c, bar->v); #endif if (bar->tm.tm_year < 99 || bar->tm.tm_year > 107 || bar->tm.tm_mon < 0 || bar->tm.tm_mon > 11 || bar->tm.tm_mday < 1 || bar->tm.tm_mday > 31 || bar->tm.tm_wday < 1 || bar->tm.tm_wday > 5 || bar->tm.tm_hour < 0 || bar->tm.tm_hour > 23 || bar->tm.tm_min < 0 || bar->tm.tm_min > 59) { #if 0 fprintf(stderr, "Bad timestamp or weekday (%d): %s", bar->tm.tm_wday, line); #endif free(bar); bar = NULL; continue; } if (bars->tail) bars->tail = bars->tail->next = bar; else bars->tail = bars->head = bar; } if (!error && ferror(f)) { perror("fgets"); error = 1; } if (error) { free_bars(bars); fclose(f); return NULL; } if (fclose(f)) { perror("fclose"); free_bars(bars); return NULL; } return bars; } static unsigned int b2i(const bar *bar) { double diff = bar->c - bar->o; if (diff > T2) return 0; if (diff > T) return 1; if (diff < -T2) return 4; if (diff < -T) return 3; return 2; } static void collect_stats(const bars *bars) { bar *bar, *prev = NULL; int h[4] = {-1, -1, -1, -1}; memset(&stats, 0, sizeof(stats)); if ((bar = bars->head)) do { h[3] = h[2]; h[2] = h[1]; h[1] = h[0]; h[0] = b2i(bar); if (h[3] >= 0) { statent *es[5], *e; int i; es[0] = &stats.cdh3 [bar->tm.tm_wday - 1][bar->tm.tm_hour] [h[3]][h[2]][h[1]]; es[1] = &stats.cdh1 [bar->tm.tm_wday - 1][bar->tm.tm_hour][h[1]]; es[2] = &stats.ch2[bar->tm.tm_hour][h[2]][h[1]]; es[3] = &stats.cd2[bar->tm.tm_wday - 1][h[2]][h[1]]; es[4] = &stats.c3[h[3]][h[2]][h[1]]; for (i = 0; i < 5; i++) { e = es[i]; if (bar->c > prev->c) { e->u += bar->c - prev->c; e->cu++; } else if (prev->c > bar->c) { e->d += prev->c - bar->c; e->cd++; } e->ct++; } } prev = bar; } while ((bar = bar->next)); } static void print_stats(void) { int wd, h, a, b, c; statent *e; double sum, diff, me, sme; count up, dp, cup, cdp, cme; sme = 0; cme = 0; for (wd = 0; wd < 5; wd++) for (h = 0; h < 24; h++) for (a = 0; a < N; a++) for (b = 0; b < N; b++) for (c = 0; c < N; c++) { e = &stats.cdh3[wd][h][a][b][c]; if (e->ct < TCTS) continue; diff = e->u - e->d; me = diff / e->ct; if (fabs(me) < TMES) continue; sum = e->u + e->d; up = e->u * 100 / sum; dp = e->d * 100 / sum; cup = e->cu * 100 / e->ct; cdp = e->cd * 100 / e->ct; printf("%u %u %u %u %u: %u: %u%%/%u%% %u%%/%u%% %.5f\n", wd + 1, h, a, b, c, e->ct, up, dp, cup, cdp, me); sme += fabs(me) * e->ct; cme += e->ct; } if (cme) printf("%.4f/%u = %.5f\n", sme, cme, sme / cme); } static double trade(const bars *bars) { double balance, equity, margin, freemargin, pl, bid, ask; double max, min, dd, maxdd, gp, gl, np, pf; position pos = { NONE, 0, 0 }; count trades = 0; bar *bar; int h[3] = {-1, -1, -1}; max = min = equity = balance = BALANCE; pl = dd = maxdd = gp = gl = 0; if ((bar = bars->head)) do { bid = bar->c; ask = bid + SPREAD; margin = pl = 0; switch (pos.dir) { case BUY: margin = bid * pos.size / LEVERAGE; pl = bid - pos.level; break; case SELL: margin = ask * pos.size / LEVERAGE; pl = pos.level - ask; case NONE: break; } equity = balance + pl * pos.size; freemargin = equity - margin; if (equity > max) max = equity; if (equity < min) min = equity; dd = max - equity; if (dd > maxdd) maxdd = dd; /* XXX: should take low & high into consideration, not only bar close prices */ if (freemargin < STOPOUT) break; h[2] = h[1]; h[1] = h[0]; h[0] = b2i(bar); if (h[2] >= 0) { statent *es[5]; int i; double me; count ct; es[0] = &stats.cdh3 [bar->tm.tm_wday - 1][bar->tm.tm_hour] [h[2]][h[1]][h[0]]; es[1] = &stats.cdh1 [bar->tm.tm_wday - 1][bar->tm.tm_hour][h[0]]; es[2] = &stats.ch2[bar->tm.tm_hour][h[1]][h[0]]; es[3] = &stats.cd2[bar->tm.tm_wday - 1][h[1]][h[0]]; es[4] = &stats.c3[h[2]][h[1]][h[0]]; me = 0; ct = 0; for (i = 0; i < 5; i++) { statent *e = es[i]; double diff; if (e->ct < TCTC) continue; diff = e->u - e->d; ct = e->ct; me = diff / ct; break; } /* XXX: we assume that closing thresholds are smaller than opening ones */ if (ct < TCTC) continue; if ((pos.dir == BUY && me < TMEC) || (pos.dir == SELL && me > -(TMEC)) || pl <= -SL || pl >= TP) { balance = equity; /* Close the position */ pos.dir = NONE; if (pl > 0) gp += pl * pos.size; else gl -= pl * pos.size; } if (pos.dir) continue; #if 1 if (h[0] == h[1] && h[1] == h[2]) continue; #endif if (ct < TCTO) continue; if (fabs(me) < TMEO) continue; /* Open a new position */ if (me > 0) { pos.dir = BUY; pos.level = ask; } else { pos.dir = SELL; pos.level = bid; } pos.size = LOT; trades++; } } while ((bar = bar->next)); balance = equity; /* Close the last trade, if any */ pos.dir = NONE; if (pl > 0) gp += pl * pos.size; else gl -= pl * pos.size; np = gp - gl; pf = gp / gl; #if 1 /* Profit must be at least 1% and larger than the maximum drawdown, and * the number of trades must be at least 20. */ if (np >= BALANCE / 100 && np > maxdd && trades >= 20) #endif { printf("balance = %.2f (min %.2f, max %.2f); drawdown = %.2f; " "trades = %u\n" "gross p/l = %.2f/%.2f; profit factor = %.2f; " "expected payoff = %.2f\n", balance, min, max, maxdd, trades, gp, gl, pf, np / trades); return balance; } return 0; } int main(int argc, const char * const *argv) { bars *b1, *b2; if (argc != 2 && argc != 3) { fprintf(stderr, "Wrong number of arguments\n"); return 1; } if (argc == 3) fprintf(stderr, "Parsing file #1: %s\n", argv[1]); b1 = b2 = parse_csv_file(argv[1]); if (!b1) return 1; if (argc == 3) { fprintf(stderr, "Parsing file #2: %s\n", argv[2]); b2 = parse_csv_file(argv[2]); if (!b2) return 1; } for (T = Tmin; T <= Tmax+1e-7; T += Tstep) for (T2 = T2min; T2 <= T2max+1e-7; T2 += T2step) { collect_stats(b1); // print_stats(); for (SL = SLmin; SL <= SLmax+1e-7; SL += SLstep) for (TP = TPmin; TP <= TPmax+1e-7; TP += TPstep) for (TCTO = TCTOmin; TCTO <= TCTOmax; TCTO += TCTOstep) for (TMEO = TMEOmin; TMEO <= TMEOmax+1e-7; TMEO += TMEOstep) for (TCTC = TCTCmin; TCTC <= TCTCmax; TCTC += TCTCstep) for (TMEC = TMECmin; TMEC <= TMECmax+1e-7; TMEC += TMECstep) { if (trade(b2) - BALANCE > 1.0) printf("SL=%.4f TP=%.4f " "T=%.4f/%.4f TCTO=%u TMEO=%.4f " "TCTC=%u TMEC=%.4f\n", SL, TP, T, T2, TCTO, TMEO, TCTC, TMEC); } } return 0; }