/**
 * https://www.forexfactory.com/thread/1270740-which-trade-setup-has-the-highest-probability
 */
package fxexpectedreturn;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

public class Simulator {
   public double STOP_LOSS = 0.00500;
   public double TAKE_PROFIT = 0.01000;
   public double BE_LEVEL = 0.00000;
   public boolean TRAILING_SL = false;
   public String FILENAME = "return-50-100.csv";
   public int NR_TICKS = 10_000_000;
   public int NR_RUNS = 200_000;
   public double START_BALANCE = 10_000.0;
   public double RISK_PERCENT = 1.0;
   public double COST = 0.00050;

   public class SimulatorWorker extends Thread {

      private AtomicInteger nrRuns;
      private FileWriter writer;

      public void init(AtomicInteger nrRuns, FileWriter writer) {
         this.nrRuns = nrRuns;
         this.writer = writer;
      }

      @Override
      public void run() {
         Random random = ThreadLocalRandom.current();

         while (nrRuns.incrementAndGet() <= NR_RUNS) {
            double price = 1.00000;
            double equity = START_BALANCE;
            int sign = 0;
            double volume = 0.0;
            double takeProfit = 0.0;
            double beLevel = 0.0;
            double stopLoss = 0.0;
            double entry = 0.0;
            int nrWins = 0;
            int nrLosses = 0;

            for (int tick = 0; tick < NR_TICKS; tick++) {
               // update price
               if (random.nextBoolean()) {
                  price *= 1.00001;
               } else {
                  price /= 1.00001;
               }

               if (sign != 0) {
                  // check stops
                  if (sign * price >= sign * takeProfit || sign * price <= sign * stopLoss) {
                     equity += sign * volume * (price - entry) - volume * COST;
                     if (sign * price > sign * entry) {
                        nrWins++;
                     } else {
                        nrLosses++;
                     }
                     sign = 0;
                  } else {
                     // check BE
                     if (beLevel != 0.0 && sign * price >= sign * beLevel) {
                        stopLoss = entry;
                     }
                     // check trailing SL
                     if (TRAILING_SL) {
                        double newSL = price - sign * STOP_LOSS;
                        if (sign * newSL > sign * stopLoss) {
                           stopLoss = newSL;
                        }
                     }
                  }
               } else {
                  // make entry
                  sign = random.nextBoolean() ? -1 : +1;
                  takeProfit = price + sign * TAKE_PROFIT;
                  stopLoss = price - sign * STOP_LOSS;
                  beLevel = (BE_LEVEL == 0.0 ? 0.0 : price + sign * BE_LEVEL);
                  double amountRisked = equity * RISK_PERCENT / 100.0;
                  volume = amountRisked / STOP_LOSS;
                  entry = price;
               }
            }

            synchronized (writer) {
               try {
                  writer.write(String.format("%.5f, %d, %d\n", equity / START_BALANCE, nrWins, nrLosses));
               } catch (IOException ex) {
                  System.err.print(ex);
                  break;
               }
            }
         }
      }
   }

   public void run() throws IOException {
      FileWriter writer = new FileWriter(FILENAME);
      AtomicInteger nrRuns = new AtomicInteger(0);

      // Start worker threads
      int nrThreads = Runtime.getRuntime().availableProcessors();
      ArrayList<SimulatorWorker> workers = new ArrayList<>();
      for (int i = 0; i < nrThreads; i++) {
         SimulatorWorker worker = new SimulatorWorker();
         worker.init(nrRuns, writer);
         workers.add(worker);
         worker.start();
      }

      // Wait for workers to complete
      for (SimulatorWorker worker : workers) {
         try {
            worker.join();
         } catch (InterruptedException ex) {
            // ignore
         }
      }

      writer.close();
      System.out.printf("Written \"%s\"\n", FILENAME);
   }
}
