public List<MarketSnapshot> load(ProgressListener progressListener) throws JBookTraderException {
    String line = "";
    int lineSeparatorSize = System.getProperty("line.separator").length();
    long sizeRead = 0, lineNumber = 0;

    List<MarketSnapshot> snapshots = new ArrayList<MarketSnapshot>();

    try {
      while ((line = reader.readLine()) != null) {
        if (lineNumber % 50000 == 0) {
          progressListener.setProgress(sizeRead, fileSize, "Loading historical data file");
          if (progressListener.isCancelled()) {
            break;
          }
        }
        lineNumber++;
        sizeRead += line.length() + lineSeparatorSize;
        boolean isComment = line.startsWith("#");
        boolean isProperty = line.contains("=");
        boolean isBlankLine = (line.trim().length() == 0);
        boolean isMarketDepthLine = !(isComment || isProperty || isBlankLine);
        if (isMarketDepthLine) {
          MarketSnapshot marketSnapshot = toMarketDepth(line);
          if (filter == null || filter.contains(time)) {
            snapshots.add(marketSnapshot);
          }
          previousTime = time;
        } else if (isProperty) {
          if (line.startsWith("timeZone")) {
            setTimeZone(line);
          }
        }
      }

      if (sdf == null) {
        String msg = "Property " + "\"timeZone\"" + " is not defined in the data file." + LINE_SEP;
        throw new JBookTraderException(msg);
      }

    } catch (IOException ioe) {
      throw new JBookTraderException("Could not read data file");
    } catch (Exception e) {
      String errorMsg = "Problem parsing line #" + lineNumber + ": " + line + LINE_SEP;
      String description = e.getMessage();
      if (description == null) {
        description = e.toString();
      }
      errorMsg += description;
      throw new RuntimeException(errorMsg);
    }

    return snapshots;
  }
  private MarketSnapshot toMarketDepth(String line) throws JBookTraderException, ParseException {
    List<String> tokens = fastSplit(line);

    if (tokens.size() != COLUMNS) {
      String msg = "The line should contain exactly " + COLUMNS + " comma-separated columns.";
      throw new JBookTraderException(msg);
    }

    String dateTime = tokens.get(0) + tokens.get(1);
    String dateTimeWithoutSeconds = dateTime.substring(0, 10);

    if (dateTimeWithoutSeconds.equals(previousDateTimeWithoutSeconds)) {
      // only seconds need to be set
      int milliSeconds = 1000 * Integer.parseInt(dateTime.substring(10));
      long previousMilliSeconds = previousTime % 60000;
      time = previousTime + (milliSeconds - previousMilliSeconds);
    } else {
      time = sdf.parse(dateTime).getTime();
      previousDateTimeWithoutSeconds = dateTimeWithoutSeconds;
    }

    if (time <= previousTime) {
      String msg =
          "Timestamp of this line is before or the same as the timestamp of the previous line.";
      throw new JBookTraderException(msg);
    }

    double balance = Double.parseDouble(tokens.get(2));
    double price = Double.parseDouble(tokens.get(3));
    int volume = Integer.parseInt(tokens.get(4));
    return new MarketSnapshot(time, balance, price, volume);
  }
  public List<OptimizationResult> call() throws JBookTraderException {
    List<Strategy> strategies = new ArrayList<>();
    List<OptimizationResult> optimizationResults = new LinkedList<>();

    MarketBook marketBook = new MarketBook();
    IndicatorManager indicatorManager = new IndicatorManager();

    for (StrategyParams params : tasks) {
      Strategy strategy = optimizerRunner.getStrategyInstance(params);
      strategy.setMarketBook(marketBook);
      strategy.setIndicatorManager(indicatorManager);
      strategy.setIndicators();
      strategies.add(strategy);
    }

    TradingSchedule tradingSchedule = strategies.get(0).getTradingSchedule();
    int strategiesCount = strategies.size();

    List<MarketSnapshot> snapshots = optimizerRunner.getSnapshots();
    long snapshotsCount = snapshots.size();
    for (int count = 0; count < snapshotsCount; count++) {
      MarketSnapshot marketSnapshot = snapshots.get(count);
      marketBook.setSnapshot(marketSnapshot);
      indicatorManager.updateIndicators();
      boolean isInSchedule = tradingSchedule.contains(marketSnapshot.getTime());
      if (count < snapshotsCount - 1) {
        // ekk-needs optimization
        isInSchedule = isInSchedule && !marketBook.isGapping(snapshots.get(count + 1));
      }

      for (Strategy strategy : strategies) {
        strategy.processInstant(isInSchedule);
      }

      if (count % 5000 == 0) {
        if (optimizerRunner.isCancelled()) {
          break;
        }
        optimizerRunner.iterationsCompleted(strategiesCount * 5000);
      }
    }

    if (!optimizerRunner.isCancelled()) {
      int minTrades = optimizerRunner.getMinTrades();

      for (Strategy strategy : strategies) {
        strategy.closePosition();

        PerformanceManager performanceManager = strategy.getPerformanceManager();
        if (performanceManager.getTrades() >= minTrades) {
          if (inclusionCriteria.equals("All strategies") || performanceManager.getNetProfit() > 0) {
            OptimizationResult optimizationResult =
                new OptimizationResult(strategy.getParams(), performanceManager);
            optimizationResults.add(optimizationResult);
          }
        }
      }
    }

    return optimizationResults;
  }