//initialize void GoldieClock::begin(void) { Adafruit_NeoPixel::begin(); //get the time zone index from eeprom and ensure that it's valid tzIndex = eeprom_read_byte( &ee_tzIndex ); if ( tzIndex >= sizeof(tzNames) / sizeof(tzNames[0]) ) { tzIndex = 0; //not valid, set to UTC eeprom_write_byte( &ee_tzIndex, tzIndex); } tz = timezones[tzIndex]; //set the tz time_t utc = getUTC(); //synchronize with RTC while ( utc == getUTC() ); //wait for increment to the next second utc = RTC.get(); setUTC(utc); //set our time from the RTC Serial << F("\nTime set from RTC:\n"); printDateTime(utc); Serial << F("UTC") << endl; time_t local = (*tz).toLocal(utc, &tcr); printDateTime(local); Serial << tcr -> abbrev << endl; // rainbowCycle(2, 2); //power-up eye candy clear(); //turn all the NeoPixels off at power up show(); }
void BoardIn16_hbeat(){ for(int i=0; i<NumberOfBoardIn16; i++){ IN16[i].read(); //~ if( !IN16[i].IsValid() &! bitRead(IN16[i].status, ADDR_HBEAT_NOTIF_KO)){ if(!IN16[i].IsValid() &! IN16[i].IsNotifiedNOK()){ IN16[i].loss_counter++; printDateTime(); Serial.print(F(" ! Warning : com KO with IN16 id=")); Serial.println(i); IN16[i].postNotifyNOK(); //~ bitSet(IN16[i].status, ADDR_HBEAT_NOTIF_KO); //~ bitClear(IN16[i].status, ADDR_HBEAT_NOTIF_OK); //~ }else if( IN16[i].IsValid() &! bitRead(IN16[i].status, ADDR_HBEAT_NOTIF_OK)){ }else if(IN16[i].IsValid() &! IN16[i].IsNotifiedOK()){ IN16[i].postNotifyOK(); //~ bitSet(IN16[i].status, ADDR_HBEAT_NOTIF_OK); //~ bitClear(IN16[i].status, ADDR_HBEAT_NOTIF_KO); printDateTime(); Serial.print(F(" ! Warning : com OK with In16 id=")); Serial.println(i); }; } }
void sendEmail(Result res, Parameters params) { char tdStyle[] = "font-family:Georgia;font-size:11px;border-color:#A1A1A1;border-width:1px;border-style:solid;padding:2px;"; char captionStyle[] = "font-family:Georgia;font-size:13px;font-weight:normal;color:#0021BF;padding-bottom:6px;text-align:left;"; char tableTitleStyle[] = "font-family:Georgia;font-variant:small-caps;font-size:13px;text-align:center;border-color:#A1A1A1;border-width:1px;border-style:solid;background-color:#EAEAEA;"; std::ostringstream oss; oss.precision(2); oss << std::fixed; oss << "sendemail -f " << params.senderAddress << " -t " << params.receiverAddress << " -u \"Blackbird Bitcoin Arbitrage - Trade " << res.id <<" ("; if (res.totPerf() >= 0) { oss << "+" << res.totPerf() * 100; } else { oss << res.totPerf() * 100; } oss << "%)\" -m \""; oss << "<html>"; oss << " <div>"; oss << " <br/><br/>"; oss << " <table style=\\\"border-width:0px;border-collapse:collapse;text-align:center;\\\">"; oss << " <caption style=\\\"" << captionStyle << "\\\">Blackbird Bitcoin Arbitrage - Trade " << res.id << "</caption>"; oss << " <tr style=\\\"" << tableTitleStyle << "\\\">"; oss << " <td style=\\\"" << tdStyle << "width:120px;\\\">Entry Date</td>"; oss << " <td style=\\\"" << tdStyle << "width:120px;\\\">Exit Date</td>"; oss << " <td style=\\\"" << tdStyle << "width:70px;\\\">Long</td>"; oss << " <td style=\\\"" << tdStyle << "width:70px;\\\">Short</td>"; oss << " <td style=\\\"" << tdStyle << "width:70px;\\\">Exposure</td>"; oss << " <td style=\\\"" << tdStyle << "width:70px;\\\">Profit</td>"; oss << " <td style=\\\"" << tdStyle << "width:70px;\\\">Return</td>"; oss << " </tr>"; oss << " <tr>"; oss << " <td style=\\\"" << tdStyle << "\\\">" << printDateTime(res.entryTime) << "</td>"; oss << " <td style=\\\"" << tdStyle << "\\\">" << printDateTime(res.exitTime) << "</td>"; oss << " <td style=\\\"" << tdStyle << "\\\">" << res.exchNameLong << "</td>"; oss << " <td style=\\\"" << tdStyle << "\\\">" << res.exchNameShort << "</td>"; oss << " <td style=\\\"" << tdStyle << "\\\">\\$" << res.exposure * 2.0 << "</td>"; oss << " <td style=\\\"" << tdStyle << "\\\">\\$" << res.aftBalUsd - res.befBalUsd << "</td>"; if (res.totPerf() >= 0) { oss << "<td style=\\\"" << tdStyle << "color:#000092;\\\">+"; } else { oss << "<td style=\\\"" << tdStyle << "color:#920000;\\\">"; } oss << res.totPerf() * 100 << "%</td></tr>"; oss << " </table>"; oss << " </div>"; oss << "</html>\" -s " << params.smtpServerAddress << " -xu " << params.senderUsername << " -xp " << params.senderPassword << " -o tls=yes -o message-content-type=html >/dev/null" << std::endl; oss.flush(); if (system(oss.str().c_str()) == -1) { std::cout << "Error with system call" << std::endl; } }
// read the system time. No argument reached void RTC_time() { Serial.print("Now : "); printDateTime(); Serial.println(); }
static QString getReceivedDate(const QObject * const object) { const MessageRenderInfo * const messageRenderInfo = qobject_cast<const MessageRenderInfo * const>(object); if (messageRenderInfo) return printDateTime(messageRenderInfo->message().receiveDate()); else return QString(); }
static QString getSentDate(const QObject * const object) { const MessageRenderInfo * const messageRenderInfo = qobject_cast<const MessageRenderInfo * const>(object); if (messageRenderInfo && messageRenderInfo->showServerTime()) return printDateTime(messageRenderInfo->message().sendDate()); else return QString(); }
static QString getReceivedDate(bool niceDateFormat, const ParserData * const object) { const MessageRenderInfo * const messageRenderInfo = dynamic_cast<const MessageRenderInfo * const>(object); if (messageRenderInfo) return printDateTime(niceDateFormat, messageRenderInfo->message().receiveDate()); else return QString(); }
static QString getSentDate(bool niceDateFormat, const ParserData * const object) { const MessageRenderInfo * const messageRenderInfo = dynamic_cast<const MessageRenderInfo * const>(object); if (messageRenderInfo && messageRenderInfo->showServerTime()) return printDateTime(niceDateFormat, messageRenderInfo->message().sendDate()); else return QString(); }
// Some parts of the code below are borrowed from Kopete project (http://kopete.kde.org/) QString AdiumChatStyleEngine::replaceKeywords(Chat *chat, QString &styleHref, QString &style) { if (!chat) return QString(""); QString result = style; QString name; //TODO: get Chat name (contacts' nicks?) if (chat) { // Replace %chatName% //TODO. Find way to dynamic update this tag (add id ?) int uinsSize = chat->contacts().count(); int i = 0; foreach (const Contact &contact, chat->contacts()) { name.append(contact.display()); if (++i < uinsSize) name.append(", "); } } result.replace(QString("%chatName%"), name); // Replace %sourceName% result.replace(QString("%sourceName%"), chat->account()->name()); // Replace %destinationName% result.replace(QString("%destinationName%"), name); // For %timeOpened%, display the date and time. TODO: get real time result.replace(QString("%timeOpened%"), printDateTime(QDateTime::currentDateTime())); //TODO 0.6.6: get real time!!! QRegExp timeRegExp("%timeOpened\\{([^}]*)\\}%"); int pos=0; while ((pos=timeRegExp.indexIn(result, pos)) != -1) result.replace(pos, timeRegExp.cap(0).length(), timeFormatter->convertTimeDate(timeRegExp.cap(1), QDateTime::currentDateTime())); QString photoIncoming; QString photoOutgoing; if (chat->contacts().count() > 1) photoIncoming = QString("file://") + styleHref + QString("Incoming/buddy_icon.png"); else { ContactAccountData *cad = (*chat->contacts().begin()).accountData(chat->account()); if (cad && !cad->avatar().pixmap().isNull()) photoIncoming = QString("file://") + cad->avatar().filePath(); else photoIncoming = QString("file://") + styleHref + QString("Incoming/buddy_icon.png"); } photoOutgoing = QString("file://") + styleHref + QString("Outgoing/buddy_icon.png"); result.replace(QString("%incomingIconPath%"), photoIncoming); result.replace(QString("%outgoingIconPath%"), photoOutgoing); return result; }
QString timestamp(time_t customtime) { QString buf; QDateTime date; time_t t; t = time(NULL); date.setTime_t(t); buf.append(printDateTime(date)); if (customtime) { date.setTime_t(customtime); buf.append(QString(" / S ") + printDateTime(date)); } return buf; }
void setDateTime(const char *strDate) { if (strlen(strDate) > 0) { time_t unix_time = atoi(strDate); if (unix_time > 0) { rtcSetTimeUnixSec(&RTCD1, unix_time); printDateTime(); return; } } scheduleMsg(&logger, "date_set Date parameter %s is wrong\r\n", strDate); }
static void getValue(const char *paramStr) { if (strEqualCaseInsensitive(paramStr, "todo")) { scheduleMsg(&logger, "something"); } #if EFI_RTC || defined(__DOXYGEN__) else if (strEqualCaseInsensitive(paramStr, "date")) { printDateTime(); } #endif }
void Result::printExitInfo(std::ofstream& logFile) { logFile << "\n[ EXIT FOUND ]" << std::endl; logFile << " Date & Time: " << printDateTime(exitTime) << std::endl; logFile << " Duration: " << getTradeLengthInMinute() << " minutes" << std::endl; logFile << " Price Long: $" << priceLongOut << " (target)" << std::endl; logFile << " Price Short: $" << priceShortOut << " (target)" << std::endl; logFile << " Spread: " << spreadOut * 100.0 << "%" << std::endl; logFile << " ---------------------------" << std::endl; logFile << " Target Perf Long: " << targetPerfLong() * 100.0 << "% (fees incl.)" << std::endl; logFile << " Target Perf Short: " << targetPerfShort() * 100.0 << "% (fees incl.)" << std::endl; logFile << " ---------------------------\n" << std::endl; }
void Result::printEntryInfo(std::ofstream& logFile) { logFile << "\n[ ENTRY FOUND ]" << std::endl; logFile << " Date & Time: " << printDateTime(entryTime) << std::endl; logFile << " Exchange Long: " << exchNameLong << " (id " << idExchLong << ")" << std::endl; logFile << " Exchange Short: " << exchNameShort << " (id " << idExchShort << ")" << std::endl; logFile << " Fees: " << feesLong * 100.0 << "% / " << feesShort * 100.0 << "%" << std::endl; logFile << " Price Long: $" << priceLongIn << " (target)" << std::endl; logFile << " Price Short: $" << priceShortIn << " (target)" << std::endl; logFile << " Spread: " << spreadIn * 100.0 << "%" << std::endl; logFile << " Cash used: $" << exposure << " on each exchange" << std::endl; logFile << " Exit Target: " << exitTarget * 100.0 << "%" << std::endl; logFile << std::endl; }
void Result::printExit() { std::cout << "\n[ EXIT FOUND ]" << std::endl; std::cout << " Date & Time: " << printDateTime(exitTime) << std::endl; std::cout << " Duration: " << getLength() << " minutes" << std::endl; std::cout << " Price Long: $" << priceLongOut << " (target)" << std::endl; std::cout << " Price Short: $" << priceShortOut << " (target)" << std::endl; std::cout << " Spread: " << spreadOut * 100.0 << "%" << std::endl; std::cout << " ---------------------------" << std::endl; std::cout << " Target Perf Long: " << perfLong() * 100.0 << "% (fees incl.)" << std::endl; std::cout << " Target Perf Short: " << perfShort() * 100.0 << "% (fees incl.)" << std::endl; std::cout << " ---------------------------\n" << std::endl; }
// set the system time. Arguments reached are (D,M,Y,H,M) void RTC_set() { Serial.print(F("Previous: ")); printDateTime(); Serial.println(); RTC.fillByYMD((int) getarg(3),(int) getarg(2),(int) getarg(1)); RTC.fillByHMS((int) getarg(4), (int) getarg(5),0); RTC.setTime(); RTC.startClock(); RTC.getTime(); RTC_time(); }
QString AdiumChatStyleEngine::replaceKeywords(Chat *chat, QString &styleHref, QString &source, MessageRenderInfo *message) { if (!chat) return QString(""); QString result = source; QString nick, contactId, service, protocolIcon, nickLink; Message msg = message->message(); // Replace sender (contact nick) result.replace(QString("%sender%"), msg.sender().display()); // Replace %screenName% (contact ID) result.replace(QString("%senderScreenName%"), msg.sender().id(chat->account())); // Replace service name (protocol name) result.replace(QString("%service%"), chat->account()->protocol()->protocolFactory()->displayName()); // Replace protocolIcon (sender statusIcon). TODO: result.replace(QString("%senderStatusIcon%"), chat->account()->protocol()->protocolFactory()->iconName()); // Replace time QDateTime time = msg.sendDate().isNull() ? msg.receiveDate(): msg.sendDate(); result.replace(QString("%time%"), printDateTime(time)); // Look for %time{X}% QRegExp timeRegExp("%time\\{([^}]*)\\}%"); int pos = 0; while ((pos = timeRegExp.indexIn(result , pos)) != -1) result.replace(pos, timeRegExp.cap(0).length(), timeFormatter->convertTimeDate(timeRegExp.cap(1), time)); // Look for %textbackgroundcolor{X}% // TODO: highlight background color: use the X value. QRegExp textBackgroundRegExp("%textbackgroundcolor\\{([^}]*)\\}%"); int textPos = 0; while ((textPos=textBackgroundRegExp.indexIn(result, textPos)) != -1) result.replace(textPos, textBackgroundRegExp.cap(0).length(), "inherit"); // Replace userIconPath QString photoPath; if (msg.type() == Message::TypeReceived) { result.replace(QString("%messageClasses%"), "message incoming"); ContactAccountData *cad = msg.sender().accountData(chat->account()); if (cad && !cad->avatar().pixmap().isNull()) photoPath = QString("file://") + cad->avatar().filePath(); else photoPath = QString("file://") + styleHref + QString("Incoming/buddy_icon.png"); } else { result.replace(QString("%messageClasses%"), "message outgoing"); photoPath = QString("file://") + styleHref + QString("Outgoing/buddy_icon.png"); } result.replace(QString("%userIconPath%"), photoPath); //Message direction ("rtl"(Right-To-Left) or "ltr"(Left-to-right)) result.replace(QString("%messageDirection%"), "ltr"); // Replace contact's color const QString colorName = message->nickColor(); QString lightColorName; QRegExp senderColorRegExp("%senderColor(?:\\{([^}]*)\\})?%"); textPos = 0; while((textPos = senderColorRegExp.indexIn(result, textPos)) != -1) { int light = 100; bool doLight = false; if(senderColorRegExp.numCaptures()>=1) light = senderColorRegExp.cap(1).toUInt(&doLight); if (doLight && lightColorName.isNull()) lightColorName = QColor(colorName).light(light).name(); result.replace(textPos ,senderColorRegExp.cap(0).length(), doLight ? lightColorName : colorName); } // Replace message TODO: do sth with formatMessage QString messageText = QString("<span>") + formatMessage(message->htmlMessageContent(), message->backgroundColor()) + QString("</span>"); result.replace(QString("%message%"), messageText); return result; }
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; }
//run the time setting state machine. must be called frequently while in set mode. //returns true when setting is complete or has timed out. bool GoldieClock::setClock(void) { static setStates_t SET_STATE; static time_t utc, local; static uint8_t newTzIndex; static int y, mth, d, h, m, maxDays; static uint16_t pixel; static uint32_t rpt; bool retval = false; //see if user wants to cancel set mode or if set mode has timed out if ( SET_STATE != SET_INIT ) { if ( btnSet.pressedFor(SET_LONGPRESS) ) { SET_STATE = SET_INIT; displayClock( getUTC() ); while ( btnSet.isPressed() ) btnSet.read(); //wait for user to release the button return true; } uint32_t ms = millis(); if ( (ms - btnSet.lastChange() >= SET_TIMEOUT) && (ms - btnIncr.lastChange() >= SET_TIMEOUT) ) { SET_STATE = SET_INIT; return true; } } switch ( SET_STATE ) { case SET_INIT: SET_STATE = SET_TZ; rpt = REPEAT_FIRST; utc = getUTC(); local = (*tz).toLocal(utc, &tcr); newTzIndex = tzIndex; displaySet( newTzIndex, WHITE ); while ( btnSet.isPressed() ) btnSet.read(); //wait for user to release the button break; case SET_TZ: if ( btnSet.wasReleased() ) //then move on to set year { SET_STATE = SET_YEAR; rpt = REPEAT_FIRST; if ( newTzIndex != tzIndex ) { tzIndex = newTzIndex; tz = timezones[tzIndex]; eeprom_write_byte( &ee_tzIndex, tzIndex); Serial << F("Time zone changed to ") << tzNames[tzIndex] << endl; } y = year(local); if ( y < 2015 || y > 2074 ) y = 2015; //year must be in range 2015-2074 pixel = y - 2000; //map to pixel if ( pixel > 60 ) pixel -= 60; displaySet( pixel, MAGENTA ); } else if ( btnIncr.wasReleased() ) { rpt = REPEAT_FIRST; if ( ++newTzIndex >= sizeof(tzNames) / sizeof(tzNames[0]) ) newTzIndex = 0; displaySet( newTzIndex, WHITE ); } else if ( btnIncr.pressedFor(rpt) ) { rpt += REPEAT_INCR; if ( ++newTzIndex >= sizeof(tzNames) / sizeof(tzNames[0]) ) newTzIndex = 0; displaySet( newTzIndex, WHITE ); } break; case SET_YEAR: if ( btnSet.wasReleased() ) { SET_STATE = SET_MON; rpt = REPEAT_FIRST; mth = month(local); displaySet( mth, CYAN ); } else if ( btnIncr.wasReleased() ) { rpt = REPEAT_FIRST; if ( ++pixel > LAST_PIXEL ) pixel = 0; displaySet( pixel, MAGENTA ); y = pixel < 15 ? 2060 + pixel : 2000 + pixel; //map pixel to year } else if ( btnIncr.pressedFor(rpt) ) { rpt += REPEAT_INCR; if ( ++pixel > LAST_PIXEL ) pixel = 0; displaySet( pixel, MAGENTA ); y = pixel < 15 ? 2060 + pixel : 2000 + pixel; //map pixel to year } break; case SET_MON: if ( btnSet.wasReleased() ) { SET_STATE = SET_DAY; rpt = REPEAT_FIRST; d = day(local); maxDays = monthDays[mth-1]; //number of days in the month if (mth == 2 && isLeap(y)) ++maxDays; //account for leap year if ( d > maxDays ) d = maxDays; displaySet( d, YELLOW ); } else if ( btnIncr.wasReleased() ) { rpt = REPEAT_FIRST; if ( ++mth > 12 ) mth = 1; //wrap from dec back to jan displaySet( mth, CYAN ); } else if ( btnIncr.pressedFor(rpt) ) { rpt += REPEAT_INCR; if ( ++mth > 12 ) mth = 1; displaySet( mth, CYAN ); } break; case SET_DAY: if ( btnSet.wasReleased() ) { SET_STATE = SET_HOUR; rpt = REPEAT_FIRST; h = hour(local); displaySet( h, RED ); } else if ( btnIncr.wasReleased() ) { rpt = REPEAT_FIRST; if ( ++d > maxDays ) d = 1; displaySet( d, YELLOW ); } else if ( btnIncr.pressedFor(rpt) ) { rpt += REPEAT_INCR; if ( ++d > maxDays ) d = 1; displaySet( d, YELLOW ); } break; case SET_HOUR: if ( btnSet.wasReleased() ) { SET_STATE = SET_MIN; rpt = REPEAT_FIRST; m = minute(local); displaySet( m, BLUE ); } else if ( btnIncr.wasReleased() ) { rpt = REPEAT_FIRST; if ( ++h > 23 ) h = 0; displaySet( h, RED ); } else if ( btnIncr.pressedFor(rpt) ) { rpt += REPEAT_INCR; if ( ++h > 23 ) h = 0; displaySet( h, RED ); } break; case SET_MIN: if ( btnSet.wasReleased() ) { SET_STATE = SET_INIT; rpt = REPEAT_FIRST; tmElements_t tm; tm.Year = CalendarYrToTm(y); tm.Month = mth; tm.Day = d; tm.Hour = h; tm.Minute = m; tm.Second = 0; local = makeTime(tm); utc = (*tz).toUTC(local); RTC.set(utc); setUTC(utc); Serial << F("\nTime set to:\n"); printDateTime(utc); Serial << F("UTC") << endl; printDateTime(local); Serial << tcr -> abbrev << endl; retval = true; } else if ( btnIncr.wasReleased() ) { rpt = REPEAT_FIRST; if ( ++m > 59 ) m = 0; displaySet( m, BLUE ); } else if ( btnIncr.pressedFor(rpt) ) { rpt += REPEAT_INCR; if ( ++m > 59 ) m = 0; displaySet( m, BLUE ); } break; } return retval; }