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; }
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; }