Exemplo n.º 1
0
int main(int argc, char **argv) {

  // header information
  std::cout << "Blackbird Bitcoin Arbitrage\nVersion 0.0.2" << std::endl;
  std::cout << "DISCLAIMER: USE THE SOFTWARE AT YOUR OWN RISK.\n" << std::endl;

  // read the config file (config.json)
  json_error_t error;
  json_t *root = json_load_file("config.json", 0, &error);
  if (!root) {
    std::cout << "ERROR: config.json incorrect (" << error.text << ")\n" << std::endl;
    return 1;
  }
 
  // get config variables
  int gapSec = json_integer_value(json_object_get(root, "GapSec"));
  unsigned  debugMaxIteration = json_integer_value(json_object_get(root, "DebugMaxIteration"));

  bool useFullCash = json_boolean_value(json_object_get(root, "UseFullCash")); 
  double untouchedCash = json_real_value(json_object_get(root, "UntouchedCash"));
  double cashForTesting = json_real_value(json_object_get(root, "CashForTesting"));

  if (!useFullCash && cashForTesting < 15.0) {
    std::cout << "ERROR: Minimum test cash is $15.00.\n" << std::endl;
    return 1;
  }
  
  // function arrays 
  getQuoteType getQuote[] = {Bitfinex::getQuote, OkCoin::getQuote, Bitstamp::getQuote, Kraken::getQuote};
  getAvailType getAvail[] = {Bitfinex::getAvail, OkCoin::getAvail, Bitstamp::getAvail, Kraken::getAvail};
  sendOrderType sendOrder[] = {Bitfinex::sendOrder, OkCoin::sendOrder, Bitstamp::sendOrder};
  isOrderCompleteType isOrderComplete[] = {Bitfinex::isOrderComplete, OkCoin::isOrderComplete, Bitstamp::isOrderComplete};
  getActivePosType getActivePos[] = {Bitfinex::getActivePos, OkCoin::getActivePos, Bitstamp::getActivePos, Kraken::getActivePos};
  getLimitPriceType getLimitPrice[] = {Bitfinex::getLimitPrice, OkCoin::getLimitPrice, Bitstamp::getLimitPrice, Kraken::getLimitPrice};

  // thousand separator
  std::locale mylocale("");
  std::cout.imbue(mylocale);

  // print precision of two digits
  std::cout.precision(2);
  std::cout << std::fixed;

  // create a parameters structure
  Parameters params(root);
  params.addExchange("Bitfinex", 0.0020, true);
  params.addExchange("OKCoin", 0.0020, false);
  params.addExchange("Bitstamp", 0.0025, false);
  params.addExchange("Kraken", 0.0025, true);

  // CSV file
  std::string csvFileName;
  csvFileName = "result_" + printDateTimeFileName() + ".csv";
  std::ofstream csvFile;
  csvFile.open(csvFileName.c_str(), std::ofstream::trunc);
  // CSV header
  csvFile << "TRADE_ID,EXCHANGE_LONG,EXHANGE_SHORT,ENTRY_TIME,EXIT_TIME,DURATION,TOTAL_EXPOSURE,BALANCE_BEFORE,BALANCE_AFTER,RETURN\n";
  csvFile.flush();

  // time structure
  time_t rawtime;
  rawtime = time(NULL);
  struct tm * timeinfo;
  timeinfo = localtime(&rawtime);
  
  bool inMarket = false;
  int resultId = 0;
  Result res;
  res.clear();
  // Vector of Bitcoin
  std::vector<Bitcoin*> btcVec;

  // create Bitcoin objects
  for (unsigned i = 0; i < params.nbExch(); ++i) {
    btcVec.push_back(new Bitcoin(i, params.exchName[i], params.fees[i], params.hasShort[i]));
  }

  // cURL
  CURL* curl;
  curl_global_init(CURL_GLOBAL_ALL);
  curl = curl_easy_init();

  std::cout << "[ Targets ]" << std::endl;
  std::cout << "   Spread to enter: " << params.spreadEntry * 100.0 << "%" << std::endl;
  std::cout << "   Spread to exit: " << params.spreadExit * 100.0  << "%" << std::endl;
  std::cout << std::endl;

  // store current balances
  std::cout << "[ Current balances ]" << std::endl;
  std::vector<double> balanceUsd;
  std::vector<double> balanceBtc;
 
  for (unsigned i = 0; i < params.nbExch(); ++i) {
    balanceUsd.push_back(getAvail[i](curl, params, "usd"));
    balanceBtc.push_back(getAvail[i](curl, params, "btc"));
  }

  // vectors that contains balances after a completed trade
  std::vector<double> newBalUsd(params.nbExch(), 0.0);
  std::vector<double> newBalBtc(params.nbExch(), 0.0);
  
  for (unsigned i = 0; i < params.nbExch(); ++i) {
    std::cout << "   " << params.exchName[i] << ":\t";
    std::cout << balanceUsd[i] << " USD\t" << std::setprecision(6) << balanceBtc[i]  << std::setprecision(2) << " BTC" << std::endl;
  }
  std::cout << std::endl;

  std::cout << "[ Cash exposure ]" << std::endl;
  if (useFullCash) {
    std::cout << "   FULL cash used!" << std::endl;
  } else {
    std::cout << "   TEST cash used\n   Value: $" << cashForTesting << std::endl;
  }
  std::cout << std::endl;

  // wait the next gapSec seconds before starting
  time(&rawtime);
  timeinfo = localtime(&rawtime);
  while ((int)timeinfo->tm_sec % gapSec != 0) {
    sleep(0.01);
    time(&rawtime);
    timeinfo = localtime(&rawtime);
  }

  // main loop
  if (!params.verbose) {
    std::cout << "Running..." << std::endl;  
  }

  unsigned currIteration = 0;
  bool stillRunning = true;
  while (stillRunning) {

    time_t currTime = mktime(timeinfo);
    time(&rawtime);

    // check if we are already too late
    if (difftime(rawtime, currTime) > 0) {
      std::cout << "WARNING: " << difftime(rawtime, currTime) << " second(s) too late at " << printDateTime(currTime) << std::endl;
      unsigned skip = (unsigned)ceil(difftime(rawtime, currTime) / gapSec);
      for (unsigned i = 0; i < skip; ++i) {
        // std::cout << "         Skipped iteration " << printDateTime(currTime) << std::endl;
        for (unsigned e = 0; e < params.nbExch(); ++e) {
          btcVec[e]->addData(currTime, btcVec[e]->getLastBid(), btcVec[e]->getLastAsk(), 0.0);
        }
        // go to next iteration
        timeinfo->tm_sec = timeinfo->tm_sec + gapSec;
        currTime = mktime(timeinfo);
      }
      std::cout << std::endl;
    }
    // wait for the next iteration
    while (difftime(rawtime, currTime) != 0) {
      sleep(0.01);
      time(&rawtime);
    }
    if (params.verbose) {
      if (!inMarket) {
        std::cout << "[ " << printDateTime(currTime) << " ]" << std::endl;
      }
      else {
        std::cout << "[ " << printDateTime(currTime) << " IN MARKET: Long " << res.exchNameLong << " / Short " << res.exchNameShort << " ]" << std::endl;
      }
    }
    // download the exchanges prices
    for (unsigned e = 0; e < params.nbExch(); ++e) {

      double bid = getQuote[e](curl, true);
      double ask = getQuote[e](curl, false);
      
      // add previous price if bid or ask is 0.0
      if (bid == 0.0) {
        bid = btcVec[e]->getLastBid();
	std::cout << "   WARNING: " << params.exchName[e] << " bid is null, use previous one" << std::endl;
      }
      if (ask == 0.0) {
        ask = btcVec[e]->getLastAsk();
	std::cout << "   WARNING: " << params.exchName[e] << " ask is null, use previous one" << std::endl;
      }


      if (params.verbose) {
        std::cout << "   " << params.exchName[e] << ": \t" << bid << " / " << ask << std::endl;
      }
      btcVec[e]->addData(currTime, bid, ask, 0.0);
      curl_easy_reset(curl);
    }
    // load data terminated
    if (params.verbose) {
      std::cout << "   ----------------------------" << std::endl;
    }
    // compute entry point
    if (!inMarket) {
      for (unsigned i = 0; i < params.nbExch(); ++i) {
        for (unsigned j = 0; j < params.nbExch(); ++j) {
          if (i != j) {
            if (checkEntry(btcVec[i], btcVec[j], res, params)) {
              // entry opportunity found
              // compute exposure
              res.exposure = std::min(balanceUsd[res.idExchLong], balanceUsd[res.idExchShort]);
              if (res.exposure == 0.0) {
                std::cout << "   WARNING: Opportunity found but no cash available. Trade canceled." << std::endl;
                break;
              }
              if (useFullCash == false && res.exposure <= cashForTesting) {
                std::cout << "   WARNING: Opportunity found but no enough cash. Need more than TEST cash (min. $" << cashForTesting << "). Trade canceled." << std::endl;
                break;        
              }

              if (useFullCash) {
                // leave untouchedCash
                res.exposure -= untouchedCash * res.exposure;
              }
              else {
	        // use test money
                res.exposure = cashForTesting;
              }

	      // check volumes
	      double volumeLong = res.exposure / btcVec[res.idExchLong]->getLastAsk();
	      double volumeShort = res.exposure / btcVec[res.idExchShort]->getLastBid();
	      double limPriceLong = getLimitPrice[res.idExchLong](curl, volumeLong, false);	
	      double limPriceShort = getLimitPrice[res.idExchShort](curl, volumeShort, true);
	      
	      if (limPriceLong - res.priceLongIn > 0.30 || res.priceShortIn - limPriceShort > 0.30) {
                std::cout << "   WARNING: Opportunity found but not enough volume. Trade canceled." << std::endl;
		break;
	      }

              inMarket = true;
              resultId++;
              // update result
              res.id = resultId;
              res.entryTime = currTime;
              res.printEntry();
              res.maxSpread[res.idExchLong][res.idExchShort] = -1.0;
              res.minSpread[res.idExchLong][res.idExchShort] = 1.0;
              int longOrderId = 0;
              int shortOrderId = 0;

              // send Long order
              longOrderId = sendOrder[res.idExchLong](curl, params, "buy", volumeLong, btcVec[res.idExchLong]->getLastAsk());
              // send Short order
              shortOrderId = sendOrder[res.idExchShort](curl, params, "sell", volumeShort, btcVec[res.idExchShort]->getLastBid());
              
              // wait for the orders to be filled
              sleep(3.0);
              std::cout << "Waiting for the two orders to be filled..." << std::endl;
              while (!isOrderComplete[res.idExchLong](curl, params, longOrderId) || !isOrderComplete[res.idExchShort](curl, params, shortOrderId)) {
                sleep(3.0);
              }
              std::cout << "Done" << std::endl;
              longOrderId = 0;
              shortOrderId = 0;
              break;
            }
          }
        }
        if (inMarket) {
          break;
        }
      }
      if (params.verbose) {
        std::cout << std::endl;
      }
    }
    // in market, looking to exit
    else if (inMarket) {

      if (checkExit(btcVec[res.idExchLong], btcVec[res.idExchShort], res, params, currTime)) {
        // exit opportunity found


        // check current exposure
        std::vector<double> btcUsed;
        for (unsigned i = 0; i < params.nbExch(); ++i) {
          btcUsed.push_back(getActivePos[i](curl, params));
        }  
       
        // check volumes
	double volumeLong = btcUsed[res.idExchLong];
	double volumeShort = btcUsed[res.idExchShort];
        double limPriceLong = getLimitPrice[res.idExchLong](curl, volumeLong, true);
        double limPriceShort = getLimitPrice[res.idExchShort](curl, volumeShort, false);
        if (res.priceLongOut - limPriceLong > 0.30 || limPriceShort - res.priceShortOut > 0.30) {
          std::cout << "   WARNING: Opportunity found but not enough volume. Trade canceled." << std::endl;
	} else {
	  res.exitTime = currTime;
          res.printExit();
          // send orders
          int longOrderId = 0;
          int shortOrderId = 0;
          std::cout << std::setprecision(6) << "BTC exposure on " << params.exchName[res.idExchLong] << ": " << volumeLong << std::setprecision(2) << std::endl;
          std::cout << std::setprecision(6) << "BTC exposure on " << params.exchName[res.idExchShort] << ": " << volumeShort << std::setprecision(2) << std::endl;
          std::cout << std::endl;
        
          // Close Long
          longOrderId = sendOrder[res.idExchLong](curl, params, "sell", fabs(btcUsed[res.idExchLong]), btcVec[res.idExchLong]->getLastBid());
          // Close Short
          shortOrderId = sendOrder[res.idExchShort](curl, params, "buy", fabs(btcUsed[res.idExchShort]), btcVec[res.idExchShort]->getLastAsk());
        
          // wait for the orders to be filled
          sleep(3.0);
          std::cout << "Waiting for the two orders to be filled..." << std::endl;
          while (!isOrderComplete[res.idExchLong](curl, params, longOrderId) || !isOrderComplete[res.idExchShort](curl, params, shortOrderId)) {
            sleep(3.0);
          }
          std::cout << "Done\n" << std::endl;
          longOrderId = 0;
          shortOrderId = 0;

          // market exited
          inMarket = false;
        
          // new balances
          for (unsigned i = 0; i < params.nbExch(); ++i) {
            newBalUsd[i] = getAvail[i](curl, params, "usd");
            newBalBtc[i] = getAvail[i](curl, params, "btc");
          }
        
          for (unsigned i = 0; i < params.nbExch(); ++i) {
            std::cout << "New balance on " << params.exchName[i] << ":  \t";
            std::cout << newBalUsd[i] << " USD (perf $" << newBalUsd[i] - balanceUsd[i] << "), ";
            std::cout << std::setprecision(6) << newBalBtc[i]  << std::setprecision(2) << " BTC" << std::endl;
          }
          std::cout << std::endl;

          // update res with total balance
          res.befBalUsd = std::accumulate(balanceUsd.begin(), balanceUsd.end(), 0.0);
          res.aftBalUsd = std::accumulate(newBalUsd.begin(), newBalUsd.end(), 0.0);

          // update current balances with new values
          for (unsigned i = 0; i < params.nbExch(); ++i) {
            balanceUsd[i] = newBalUsd[i];
            balanceBtc[i] = newBalBtc[i];
          }

          // display performance
          std::cout << "ACTUAL PERFORMANCE: " << "$" << res.aftBalUsd - res.befBalUsd << " (" << res.totPerf() * 100.0 << "%)\n" << std::endl; 
        
          // new csv line with result
          csvFile << res.id << "," << res.exchNameLong << "," << res.exchNameShort << "," << printDateTimeCsv(res.entryTime) << "," << printDateTimeCsv(res.exitTime);
          csvFile << "," << res.getLength() << "," << res.exposure * 2.0 << "," << res.befBalUsd << "," << res.aftBalUsd << "," << res.totPerf() << "\n";
          csvFile.flush();

          // send email
          if (params.sendEmail) {
            sendEmail(res, params);
            std::cout << "Email sent" << std::endl;
          }

          // delete result information
          res.clear();
        
          // if "stop_after_exit" file exists then return
          std::ifstream infile("stop_after_exit");
          if (infile.good()) {
            std::cout << "Exit after last trade (file stop_after_exit found)" << std::endl;
            stillRunning = false;
          }
        }
      }
      if (params.verbose) {
        std::cout << std::endl;
      }
    }
    // activities for this iteration terminated
    timeinfo->tm_sec = timeinfo->tm_sec + gapSec;
    currIteration++;
    if (currIteration >= debugMaxIteration) {
      std::cout << "Max iteration reached (" << debugMaxIteration << ")" <<std::endl;
      stillRunning = false;
    }
  }
  // delete Bitcoin objects
  for (unsigned i = 0; i < params.nbExch(); ++i) {
    delete(btcVec[i]);
  }
  json_decref(root);
  // close cURL
  curl_easy_cleanup(curl);
  curl_global_cleanup();
  // close csv file
  csvFile.close();
  
  return 0;
}
Exemplo n.º 2
0
int main(int argc, char** argv) {
  std::cout << "Blackbird Bitcoin Arbitrage" << std::endl;
  std::cout << "DISCLAIMER: USE THE SOFTWARE AT YOUR OWN RISK\n" << std::endl;

  std::locale mylocale("");

  Parameters params("blackbird.conf");

  if (!params.demoMode) {
    if (!params.useFullCash) {
      if (params.cashForTesting < 10.0) {
        std::cout << "WARNING: Minimum test cash recommended: $10.00\n" << std::endl;
      }
      if (params.cashForTesting > params.maxExposure) {
        std::cout << "ERROR: Test cash ($" << params.cashForTesting << ") is above max exposure ($" << params.maxExposure << ")\n" << std::endl;
        return -1;
      }
    }
  }

  getQuoteType getQuote[10];
  getAvailType getAvail[10];
  sendOrderType sendOrder[10];
  isOrderCompleteType isOrderComplete[10];
  getActivePosType getActivePos[10];
  getLimitPriceType getLimitPrice[10];

  int index = 0;
  
  if (params.bitfinexApi.empty() == false || params.demoMode == true) {
    params.addExchange("Bitfinex", params.bitfinexFees, params.bitfinexCanShort, true);
    getQuote[index] = Bitfinex::getQuote;
    getAvail[index] = Bitfinex::getAvail;
    sendOrder[index] = Bitfinex::sendOrder;
    isOrderComplete[index] = Bitfinex::isOrderComplete;
    getActivePos[index] = Bitfinex::getActivePos;
    getLimitPrice[index] = Bitfinex::getLimitPrice;
    index++;
  }
  if (params.okcoinApi.empty() == false || params.demoMode == true) {
    params.addExchange("OKCoin", params.okcoinFees, params.okcoinCanShort, true);
    getQuote[index] = OkCoin::getQuote;
    getAvail[index] = OkCoin::getAvail;
    sendOrder[index] = OkCoin::sendOrder;
    isOrderComplete[index] = OkCoin::isOrderComplete;
    getActivePos[index] = OkCoin::getActivePos;
    getLimitPrice[index] = OkCoin::getLimitPrice;
    index++;
  }
  if (params.bitstampClientId.empty() == false || params.demoMode == true) {
    params.addExchange("Bitstamp", params.bitstampFees, params.bitstampCanShort, true);
    getQuote[index] = Bitstamp::getQuote;
    getAvail[index] = Bitstamp::getAvail;
    sendOrder[index] = Bitstamp::sendOrder;
    isOrderComplete[index] = Bitstamp::isOrderComplete;
    getActivePos[index] = Bitstamp::getActivePos;
    getLimitPrice[index] = Bitstamp::getLimitPrice;
    index++;
  }
  if (params.geminiApi.empty() == false || params.demoMode == true) {
    params.addExchange("Gemini", params.geminiFees, params.geminiCanShort, true);
    getQuote[index] = Gemini::getQuote;
    getAvail[index] = Gemini::getAvail;
    sendOrder[index] = Gemini::sendOrder;
    isOrderComplete[index] = Gemini::isOrderComplete;
    getActivePos[index] = Gemini::getActivePos;
    getLimitPrice[index] = Gemini::getLimitPrice;
    index++;
  }
  if (params.krakenApi.empty() == false || params.demoMode == true) {
    params.addExchange("Kraken", params.krakenFees, params.krakenCanShort, true);
    getQuote[index] = Kraken::getQuote;
    getAvail[index] = Kraken::getAvail;
    sendOrder[index] = Kraken::sendOrder;
    isOrderComplete[index] = Kraken::isOrderComplete;
    getActivePos[index] = Kraken::getActivePos;
    getLimitPrice[index] = Kraken::getLimitPrice;
    index++;
  }
  if (params.itbitApi.empty() == false || params.demoMode == true) {
    params.addExchange("ItBit", params.itbitFees, params.itbitCanShort, false);
    getQuote[index] = ItBit::getQuote;
    getAvail[index] = ItBit::getAvail;
    // sendOrder[index] = ItBit::sendOrder;
    // isOrderComplete[index] = ItBit::isOrderComplete;
    getActivePos[index] = ItBit::getActivePos;
    getLimitPrice[index] = ItBit::getLimitPrice;
    index++;
  }
  if (params.btceApi.empty() == false || params.demoMode == true) {
    params.addExchange("BTC-e", params.btceFees, params.btceCanShort, false);
    getQuote[index] = BTCe::getQuote;
    getAvail[index] = BTCe::getAvail;
    // sendOrder[index] = BTCe::sendOrder;
    // isOrderComplete[index] = BTCe::isOrderComplete;
    getActivePos[index] = BTCe::getActivePos;
    getLimitPrice[index] = BTCe::getLimitPrice;
    index++;
  }
  if (params.sevennintysixApi.empty() == false || params.demoMode == true) {
    params.addExchange("796.com", params.sevennintysixFees, params.sevennintysixCanShort, true);
    getQuote[index] = SevenNintySix::getQuote;
    getAvail[index] = SevenNintySix::getAvail;
    sendOrder[index] = SevenNintySix::sendOrder;
    isOrderComplete[index] = SevenNintySix::isOrderComplete;
    getActivePos[index] = SevenNintySix::getActivePos;
    getLimitPrice[index] = SevenNintySix::getLimitPrice;
    index++;
  }


  if (index < 2) {
    std::cout << "ERROR: Blackbird needs at least two Bitcoin exchanges. Please edit the config.json file to add new exchanges\n" << std::endl;
    return -1;
  }

  std::string currDateTime = printDateTimeFileName();
  std::string csvFileName = "blackbird_result_" + currDateTime + ".csv";
  std::ofstream csvFile;
  csvFile.open(csvFileName.c_str(), std::ofstream::trunc);
  csvFile << "TRADE_ID,EXCHANGE_LONG,EXHANGE_SHORT,ENTRY_TIME,EXIT_TIME,DURATION,TOTAL_EXPOSURE,BALANCE_BEFORE,BALANCE_AFTER,RETURN\n";
  csvFile.flush();

  std::string logFileName = "blackbird_log_" + currDateTime + ".log";
  std::ofstream logFile;
  logFile.open(logFileName.c_str(), std::ofstream::trunc);
  logFile.imbue(mylocale);
  logFile.precision(2);
  logFile << std::fixed;
  params.logFile = &logFile;
  logFile << "--------------------------------------------" << std::endl;
  logFile << "|   Blackbird Bitcoin Arbitrage Log File   |" << std::endl;
  logFile << "--------------------------------------------\n" << std::endl;
  logFile << "Blackbird started on " << printDateTime() << "\n" << std::endl;

  if (params.demoMode) {
    logFile << "Demo mode: trades won't be generated\n" << std::endl;
  }

  std::cout << "Log file generated: " << logFileName << "\nBlackbird is running... (pid " << getpid() << ")\n" << std::endl;

  // Vector of Bitcoin objects
  std::vector<Bitcoin*> btcVec;
  int num_exchange = params.nbExch();
  // create Bitcoin objects
  for (int i = 0; i < num_exchange; ++i) {
    btcVec.push_back(new Bitcoin(i, params.exchName[i], params.fees[i], params.canShort[i], params.isImplemented[i]));
  }
  curl_global_init(CURL_GLOBAL_ALL);
  params.curl = curl_easy_init();

  logFile << "[ Targets ]" << std::endl;
  logFile << "   Spread Entry:  " << params.spreadEntry * 100.0 << "%" << std::endl;
  logFile << "   Spread Target: " << params.spreadTarget * 100.0  << "%" << std::endl;
  if (params.spreadEntry <= 0.0) {
    logFile << "   WARNING: Spread Entry should be positive" << std::endl;
  }
  if (params.spreadTarget <= 0.0) {
    logFile << "   WARNING: Spread Target should be positive" << std::endl;
  }
  logFile << std::endl;
  // store current balances
  logFile << "[ Current balances ]" << std::endl;
  double* balanceUsd = (double*)malloc(sizeof(double) * num_exchange);
  double* balanceBtc = (double*)malloc(sizeof(double) * num_exchange);
  for (int i = 0; i < num_exchange; ++i) {
    if (params.demoMode) {
      balanceUsd[i] = 0.0;
      balanceBtc[i] = 0.0;
    } else {
      balanceUsd[i] = getAvail[i](params, "usd");
      balanceBtc[i] = getAvail[i](params, "btc");
    }
  }
  // contains balances after a completed trade
  double* newBalUsd = (double*)malloc(sizeof(double) * num_exchange);
  double* newBalBtc = (double*)malloc(sizeof(double) * num_exchange);
  memset(newBalUsd, 0.0, sizeof(double) * num_exchange);
  memset(newBalBtc, 0.0, sizeof(double) * num_exchange);

  for (int i = 0; i < num_exchange; ++i) {
    logFile << "   " << params.exchName[i] << ":\t";
    logFile << balanceUsd[i] << " USD\t" << std::setprecision(6) << balanceBtc[i]  << std::setprecision(2) << " BTC" << std::endl;
    if (balanceBtc[i] > 0.0300) {
      logFile << "ERROR: All BTC accounts must be empty before starting Blackbird" << std::endl;
      return -1;
    }
  }
  logFile << std::endl;
  logFile << "[ Cash exposure ]" << std::endl;
  if (params.demoMode) {
    logFile << "   No cash - Demo mode" << std::endl;
  } else {
    if (params.useFullCash) {
      logFile << "   FULL cash used!" << std::endl;
    } else {
      logFile << "   TEST cash used\n   Value: $" << params.cashForTesting << std::endl;
    }
  }
  logFile << std::endl;

  time_t rawtime;
  rawtime = time(NULL);
  struct tm* timeinfo;
  timeinfo = localtime(&rawtime);
  // wait the next gapSec seconds before starting
  time(&rawtime);
  timeinfo = localtime(&rawtime);
  while ((int)timeinfo->tm_sec % params.gapSec != 0) {
    sleep(0.01);
    time(&rawtime);
    timeinfo = localtime(&rawtime);
  }
  // main loop
  if (!params.verbose) {
    logFile << "Running..." << std::endl;
  }
  bool inMarket = false;
  int resultId = 0;
  Result res;
  res.clear();
  unsigned currIteration = 0;
  bool stillRunning = true;
  time_t currTime;
  time_t diffTime;
  while (stillRunning) {

    currTime = mktime(timeinfo);
    time(&rawtime);
    diffTime = difftime(rawtime, currTime);
    // check if we are already too late
    if (diffTime > 0) {
      logFile << "WARNING: " << diffTime << " second(s) too late at " << printDateTime(currTime) << std::endl;
      // unsigned skip = (unsigned)ceil(diffTime / gapSec);
      // go to next iteration
      timeinfo->tm_sec = timeinfo->tm_sec + (ceil(diffTime / params.gapSec) + 1) * params.gapSec;
      currTime = mktime(timeinfo);
      sleep(params.gapSec - (diffTime % params.gapSec));
      logFile << std::endl;
    }
    else if (diffTime < 0) {
      sleep(-difftime(rawtime, currTime));  // sleep until the next iteration
    }
    if (params.verbose) {
      if (!inMarket) {
        logFile << "[ " << printDateTime(currTime) << " ]" << std::endl;
      } else {
        logFile << "[ " << printDateTime(currTime) << " IN MARKET: Long " << res.exchNameLong << " / Short " << res.exchNameShort << " ]" << std::endl;
      }
    }
    for (int e = 0; e < num_exchange; ++e) {
      double bid = getQuote[e](params, true);
      double ask = getQuote[e](params, false);
      if (bid == 0.0) {
        logFile << "   WARNING: " << params.exchName[e] << " bid is null, use previous one" << std::endl;
      }
      if (ask == 0.0) {
        logFile << "   WARNING: " << params.exchName[e] << " ask is null, use previous one" << std::endl;
      }
      if (params.verbose) {
        logFile << "   " << params.exchName[e] << ": \t" << bid << " / " << ask << std::endl;
      }
      btcVec[e]->updateData(bid, ask, 0.0);
      curl_easy_reset(params.curl);
    }
    if (params.verbose) {
      logFile << "   ----------------------------" << std::endl;
    }

    // compute entry point
    if (!inMarket) {
      for (int i = 0; i < num_exchange; ++i) {
        for (int j = 0; j < num_exchange; ++j) {
          if (i != j) {
            if (checkEntry(btcVec[i], btcVec[j], res, params)) {
              // entry opportunity found
              res.exposure = std::min(balanceUsd[res.idExchLong], balanceUsd[res.idExchShort]);
              if (params.demoMode) {
                logFile << "INFO: Opportunity found but no trade will be generated (Demo mode)" << std::endl;
                break;
              }
              if (res.exposure == 0.0) {
                logFile << "WARNING: Opportunity found but no cash available. Trade canceled" << std::endl;
                break;
              }
              if (params.useFullCash == false && res.exposure <= params.cashForTesting) {
                logFile << "WARNING: Opportunity found but no enough cash. Need more than TEST cash (min. $" << params.cashForTesting << "). Trade canceled" << std::endl;
                break;
              }
              if (params.useFullCash) {
                res.exposure -= params.untouchedCash * res.exposure;  // leave untouchedCash
                if (res.exposure > params.maxExposure) {
                  logFile << "WARNING: Opportunity found but exposure ($" << res.exposure << ") above the limit" << std::endl;
                  logFile << "         Max exposure will be used instead ($" << params.maxExposure << ")" << std::endl;
                  res.exposure = params.maxExposure;
                }
              } else {
                res.exposure = params.cashForTesting;  // use test money
              }
              double volumeLong = res.exposure / btcVec[res.idExchLong]->getAsk();
              double volumeShort = res.exposure / btcVec[res.idExchShort]->getBid();
              double limPriceLong = getLimitPrice[res.idExchLong](params, volumeLong, false);
              double limPriceShort = getLimitPrice[res.idExchShort](params, volumeShort, true);
              if (limPriceLong - res.priceLongIn > params.priceDeltaLim || res.priceShortIn - limPriceShort > params.priceDeltaLim) {
                logFile << "WARNING: Opportunity found but not enough liquidity. Trade canceled" << std::endl;
                logFile << "         Target long price:  " << res.priceLongIn << ", Real long price:  " << limPriceLong << std::endl;
                logFile << "         Target short price: " << res.priceShortIn << ", Real short price: " << limPriceShort << std::endl;
                res.trailing[res.idExchLong][res.idExchShort] = -1.0;
                break;
              }
              inMarket = true;
              resultId++;
              // update result
              res.id = resultId;
              res.entryTime = currTime;
              res.printEntry(*params.logFile);
              res.maxSpread[res.idExchLong][res.idExchShort] = -1.0;
              res.minSpread[res.idExchLong][res.idExchShort] = 1.0;
              res.trailing[res.idExchLong][res.idExchShort] = 1.0;
              int longOrderId = 0;
              int shortOrderId = 0;
              // send orders
              longOrderId = sendOrder[res.idExchLong](params, "buy", volumeLong, btcVec[res.idExchLong]->getAsk());
              shortOrderId = sendOrder[res.idExchShort](params, "sell", volumeShort, btcVec[res.idExchShort]->getBid());
              // wait for the orders to be filled
              logFile << "Waiting for the two orders to be filled..." << std::endl;
              sleep(3.0);
              while (!isOrderComplete[res.idExchLong](params, longOrderId) || !isOrderComplete[res.idExchShort](params, shortOrderId)) {
                sleep(3.0);
              }
              logFile << "Done" << std::endl;
              longOrderId = 0;
              shortOrderId = 0;
              break;
            }
          }
        }
        if (inMarket) {
          break;
        }
      }
      if (params.verbose) {
        logFile << std::endl;
      }
    }
    // in market, looking to exit
    else if (inMarket) {
      if (checkExit(btcVec[res.idExchLong], btcVec[res.idExchShort], res, params, currTime)) {
        // exit opportunity found
        // check current exposure
        double* btcUsed = (double*)malloc(sizeof(double) * num_exchange);
        for (int i = 0; i < num_exchange; ++i) {
          btcUsed[i] = getActivePos[i](params);
        }
        double volumeLong = btcUsed[res.idExchLong];
        double volumeShort = btcUsed[res.idExchShort];
        double limPriceLong = getLimitPrice[res.idExchLong](params, volumeLong, true);
        double limPriceShort = getLimitPrice[res.idExchShort](params, volumeShort, false);

        if (res.priceLongOut - limPriceLong > params.priceDeltaLim || limPriceShort - res.priceShortOut > params.priceDeltaLim) {
          logFile << "WARNING: Opportunity found but not enough liquidity. Trade canceled" << std::endl;
          logFile << "         Target long price:  " << res.priceLongOut << ", Real long price:  " << limPriceLong << std::endl;
          logFile << "         Target short price: " << res.priceShortOut << ", Real short price: " << limPriceShort << std::endl;
          res.trailing[res.idExchLong][res.idExchShort] = 1.0;
        } else {
          res.exitTime = currTime;
          res.printExit(*params.logFile);
          int longOrderId = 0;
          int shortOrderId = 0;
          logFile << std::setprecision(6) << "BTC exposure on " << params.exchName[res.idExchLong] << ": " << volumeLong << std::setprecision(2) << std::endl;
          logFile << std::setprecision(6) << "BTC exposure on " << params.exchName[res.idExchShort] << ": " << volumeShort << std::setprecision(2) << std::endl;
          logFile << std::endl;
          // send orders
          longOrderId = sendOrder[res.idExchLong](params, "sell", fabs(btcUsed[res.idExchLong]), btcVec[res.idExchLong]->getBid());
          shortOrderId = sendOrder[res.idExchShort](params, "buy", fabs(btcUsed[res.idExchShort]), btcVec[res.idExchShort]->getAsk());
          // wait for the orders to be filled
          logFile << "Waiting for the two orders to be filled..." << std::endl;
          sleep(3.0);
          while (!isOrderComplete[res.idExchLong](params, longOrderId) || !isOrderComplete[res.idExchShort](params, shortOrderId)) {
            sleep(3.0);
          }
          logFile << "Done\n" << std::endl;
          longOrderId = 0;
          shortOrderId = 0;
          inMarket = false;
          // new balances
          for (int i = 0; i < num_exchange; ++i) {
            newBalUsd[i] = getAvail[i](params, "usd");
            newBalBtc[i] = getAvail[i](params, "btc");
          }
          for (int i = 0; i < num_exchange; ++i) {
            logFile << "New balance on " << params.exchName[i] << ":  \t";
            logFile << newBalUsd[i] << " USD (perf $" << newBalUsd[i] - balanceUsd[i] << "), ";
            logFile << std::setprecision(6) << newBalBtc[i]  << std::setprecision(2) << " BTC" << std::endl;
          }
          logFile << std::endl;
          // update res with total balance
          for (int i = 0; i < num_exchange; ++i) {
            res.befBalUsd += balanceUsd[i];
            res.aftBalUsd += newBalUsd[i];
          }
          // update current balances with new values
          for (int i = 0; i < num_exchange; ++i) {
            balanceUsd[i] = newBalUsd[i];
            balanceBtc[i] = newBalBtc[i];
          }
          logFile << "ACTUAL PERFORMANCE: " << "$" << res.aftBalUsd - res.befBalUsd << " (" << res.totPerf() * 100.0 << "%)\n" << std::endl;
          csvFile << res.id << "," << res.exchNameLong << "," << res.exchNameShort << "," << printDateTimeCsv(res.entryTime) << "," << printDateTimeCsv(res.exitTime);
          csvFile << "," << res.getLength() << "," << res.exposure * 2.0 << "," << res.befBalUsd << "," << res.aftBalUsd << "," << res.totPerf() << "\n";
          csvFile.flush();
          if (params.sendEmail) {
            sendEmail(res, params);
            logFile << "Email sent" << std::endl;
          }
          res.clear();
          std::ifstream infile("stop_after_exit");
          if (infile.good()) {
            logFile << "Exit after last trade (file stop_after_exit found)" << std::endl;
            stillRunning = false;
          }
        }
      }
      if (params.verbose) {
        logFile << std::endl;
      }
    }
    timeinfo->tm_sec = timeinfo->tm_sec + params.gapSec;
    currIteration++;
    if (currIteration >= params.debugMaxIteration) {
      logFile << "Max iteration reached (" << params.debugMaxIteration << ")" <<std::endl;
      stillRunning = false;
    }
  }
  for (int i = 0; i < num_exchange; ++i) {
    delete(btcVec[i]);
  }
  curl_easy_cleanup(params.curl);
  curl_global_cleanup();
  csvFile.close();
  logFile.close();

  return 0;
}