Jak zrobić sobie system powiadomień

Wstęp

Dużo się ostatnio dzieje na rynku kryptowalut, nie chcielibyśmy czegoś przespać, zostać z portfelem monet kupionych po 1000 jeśli ich kurs własnie spada do 300, nie chcielibyśmy też przegapić okazji do uzupełnienia portfela po korzystnym kursie podczas głębokiej korekty. Jest wiele serwisów które oferują wysyłanie powiadomień związanych z notowaniami kryptowalut ale nie ma to jak własne narzędzie, które będzie zachowywało się dokładnie tak, jak tego oczekujemy. Jak to zaraz pokażę, nie potrzebujemy do tego żadnego serwera, wystarczy konto w Gmailu.

Narzędzie

Google, w ramach Google Apps daje dostęp do Google Apps Script, języka skryptowego opartego na JavaScript, który pozwala na robienie różnych fajnych rzeczy w dokumentach Goole (Docs, Sheets, Forms itd.). W szczególności można osadzić, np. w arkuszu kalkulacyjnym, skrypt, który będzie się uruchamiał co określony okres czasu. Można w ten sposób zbierać statystyki z historycznymi notowaniami kryptowalut albo wysłać sobie powiadomienie jeśli kurs osiągnie określony poziom, można też zrobić różne, bardziej skomplikowane rzeczy ale o tym kiedy indziej. :)

Należy zacząć od stworzenia skoroszytu (spreadsheet), w którym umieścimy nasz skrypt. W treści artykułu będę wklejał screenshoty z angielskiej wersji Google Apps gdyż nie cierpię komputerowych tłumaczeń na język polski. Z języków programowania przetłumaczono na razie tylko LOGO i niech tak zostanie :)

Ja nazwałem swój arkusz Bitcoin alert, ale jak to dalej pokażę powiadomienia będą dotyczyły różnych walut. Po jego utworzeniu należy otworzyć edytor skryptów, tak jak to pokazano na poniższym screenshocie.

Bitcoin alert

Następnie wybieramy utworzenie pustego skrypt (Blank Project) i kasujemy przykładowy kod funkcji myFunction() i zaczynami zabawę.

Pobieranie kursów

Aby pobrać notowania trzeba znać API, które umożliwia ich pobranie. W tym tekście ograniczę się do notować z BTC-e, pln.bitcurex.com oraz kantorinternetowy.pl ale analogicznie można postąpić z dowolnym innym serwisem udostępniającym notowania w json-ie. Całe API od BTC-e jest opisane pod adresem: https://hdbtce.kayako.com/Knowledgebase/Article/View/28/4/public-api, API Bitcurexa: https://bitcurex.com/pl-pages,plnapi.html a API kantorinternetowy.pl nie jest nigdzie opisane, zrobiłem reverse engineering ich strony www.  Zacznijmy więc od czegoś prostego, napiszmy skrypt, który wyśle nam aktualny kurs LTC/BTC z BTC-e.com:

function mailAlert1() {
  // Pobierz aktualny kurs LTC/BTC
  var ticker = Utilities.jsonParse(UrlFetchApp.fetch('https://btc-e.com/api/2/ltc_btc/ticker').getContentText());
  var LTCBTC_ask = ticker['ticker']['buy'];
  var LTCBTC_bid = ticker['ticker']['sell'];
  
  MailApp.sendEmail("moj-adres@domena.com", "Aktualny kurs LTC/BTC", "BID: "+LTCBTC_bid+"  ASK: "+LTCBTC_ask);
}

Po wpisaniu tego kodu należy go zapisać (CTRL+S), jesli jeszcze nie wybraliśmy nazwy dla projektów zostaniemy o nią zapytani, ja podałem „Bitcoin alert”. Następnie należny wybrać z listy funkcję mailAlert1 i uruchomić.

Uruchomienie skryptu

Zostaniemy wówczas zapytani o autoryzację uprawnień dla naszego skryptu (w końcu ma w naszym miejscu wysyłać maile).

Konieczna autoryzacja

Oczywiście, należy zaakceptować listę wymaganych uprawnień.

Autoryzacja

No i jeśli jeszcze tego nie zrobiliśmy należy ustawić prawidłowy adres email zamiast nasz@adres.pl. Może to być np. adres bramki SMS, która prześle maila wprost na nasz telefon (jeśli ktoś jest taki oldskool-owy i ma telefon bez obsługi maila ;). Jeśli podamy nasz adres na gmailu powinniśmy dostać maila w przeciąg kilku sekund.

Zapisywanie notowań w arkuszu

No tak, kto potrzebowałby notować LTC/BTC na maila, w dodatku jeśli skrypt trzeba by uruchamiać ręcznie z edytora? Zróbmy więc coś bardziej ambitnego. Zanim jednak uruchomisz poniższy skrypt koniecznie wpisz w arkuszu, z którym jest on powiązany następujące wartości.

Ustaw arkusz

function mailAlert2() {
  // Otwórz aktualny arkusz
  var ss = SpreadsheetApp.openById("0As5p2byd8rBSdE15bXhfRThJd3BleVFpR0c5OEVTSGc");
  
  // Licznik w A1
  var counterRange = ss.getSheets()[0].getRange("A1");
  var counter=counterRange.getValue();
  
  // Pobierz aktualne kursy
  var ticker = Utilities.jsonParse(UrlFetchApp.fetch('https://btc-e.com/api/2/btc_usd/ticker').getContentText());
  var BTCUSD_ask = ticker['ticker']['buy'];
  var BTCUSD_bid = ticker['ticker']['sell'];
    
  var ticker = Utilities.jsonParse(UrlFetchApp.fetch('https://btc-e.com/api/2/ltc_usd/ticker').getContentText());
  var LTCUSD_ask = ticker['ticker']['buy'];
  var LTCUSD_bid = ticker['ticker']['sell'];
    
  var ticker = Utilities.jsonParse(UrlFetchApp.fetch('https://internetowykantor.pl/cms/currency/').getContentText());
  var PLNUSD_ask = ticker['rates']['USD']['selling_rate'];
  var PLNUSD_bid = ticker['rates']['USD']['buying_rate'];

  var ticker = Utilities.jsonParse(UrlFetchApp.fetch('https://pln.bitcurex.com/data/ticker.json').getContentText());
  var BTCPLN_ask = ticker['sell'];
  var BTCPLN_bid = ticker['buy'];
  
  // Zapisz w arkuszu
  var date = Utilities.formatDate(new Date(), "GMT+1", "yyyy-MM-dd HH:mm");
  var range=ss.getSheets()[0].getRange(counter,1,1,9);
  var values = [
    [date, BTCUSD_bid,BTCUSD_ask, LTCUSD_bid,LTCUSD_ask, PLNUSD_bid,PLNUSD_ask, BTCPLN_bid,BTCPLN_ask]
    ];
  range.setValues(values);
  
  // Zwiększ licznik
  counterRange.setValue(counter+1); 
}

Każdorazowe uruchomienie tego skryptu spowoduje dopisanie do arkusza kolejnego wiersza z aktualnie pobranymi kursami. Przy okazji warto zwrócić uwagę na nieścisłość w zapisie cen Bid i Ask (raz Bid jest jako sell a raz jako Ask). Jeśli ktoś się w tym nie łapie to najprościej zapamiętać, że cena Bid jest niższa od Ask. Nie jest to niestety miejsce na dalsze wyjaśnienia :)

Teraz możemy przystąpić do zdefiniowania triggera, który będzie się uruchamiał co minutę i zapisywał notowania. Wybieramy z paska narzędzi ikonkę przypominającą zegarek.

toolbar

Następnie konfigurujemy trigger, np. tak, jak poniżej:
trigger

No i tutaj niespodzianka bo trigger nie zadziała! Skrypty odpalane z trigera czasowego nie mają bowiem aktywnego arkusza. Trzeba taki arkusz otworzyć w programie i dopiero wówczas można do niego pisać. Do tego trzeba poznać ID naszego arkusza. Nic prostszego, znajdziemy je w URL-u naszego arkusza.

ID arkusza

Teraz wystarczy zmienić linię:

var ss = SpreadsheetApp.getActive();

na

var ss = SpreadsheetApp.openById("0As5p2byd8rBSdE15bXhfRThJd3BleVFpR0c5OEVTSGc");

gdzie 0As5p2byd8rBSdE15bXhfRThJd3BleVFpR0c5OEVTSGc jest oczywiście naszym ID i skrypt powinien zacząć się zapełniać danymi w regularnych odstępach. Możemy sobie potem zrobić z nich wykresik, np. taki:

wykresik

Możemy też przeprowadzić dowolne inne analizy.

Wysyłanie alarmów

Jeśli znamy już podstawy na temat pobierania notować oraz obsługi arkusza możemy przystąpić do napisania swojego systemu powiadomień. Przykład będzie oparty o notowania LTCUSD dla BTC-e, ale analogicznie możemy sobie skonstruować narzędzie dla dowolnego z pokazanych wyżej notowań lub innego, który nauczyliśmy się pobierać.

Zacznijmy od usunięcia triggera dla funkcji mailAlert2 i wyczyszczenia arkusza (możemy też otworzyć zupełnie nowy arkusz). Następnie przygotujmy dane w następującej postaci:

Ustaw arkusz

Kolumna A zawiera poszeregowane rosnąco (to ważne, w przeciwnym wypadku będzie źle działać!) poziomy, których przekroczenie ma wywołać wysłanie alarmu. Podane wartości są przykładowe, może ich być więcej, ważna jest tylko uporządkowana kolejność. Kolumna B powinna zawierać same 0 oznaczające, że poziom nie był aktywowany. Pole D1 musi zawierać 0, w przeciwnym wypadku pierwsze wywołanie skryptu zakończy się błędem a w konsekwencji każde następne.

Sam skrypt nie jest zbyt skomplikowany. Został napisany w ten sposób, aby można go było uruchamiać co minutę, a jednocześnie aby nie wysyłał alarmów jeśli kurs będzie oscylował wokół jakiegoś poziomu. Przekroczenie danego poziomu dezaktywuje go do czasu aż notowania przekroczą inny poziom. W przypadku nagłych wahań kursu, albo wąskich odstępów między przedziałami jeśli zmiana notować przekracza więcej niż 1 poziom zostanie wysłany oddzielny mail dla każdego poziomu.

function sendMail(level, price) {
  var subject="LTC/BTC przebił poziom "+level;
  var body="O goddzinie "+ Utilities.formatDate(new Date(), "GMT+1", "HH:mm:ss yyyy-MM-dd")+" notwania LTC/BTC przebił zdefionowany poziom "+level+"\n"+
      "Aktualny, średni kurs LTC/BTC wynosi: "+price+"\n";
  MailApp.sendEmail("moj-adres@domena.pl", subject, body);
}

function mailAlert3() {
  // Otwórz aktualny arkusz
  var ss = SpreadsheetApp.openById("0As5p2byd8rBSdE15bXhfRThJd3BleVFpR0c5OEVTSGc");
  
  // Pobierz kurs
  var ticker = Utilities.jsonParse(UrlFetchApp.fetch('https://btc-e.com/api/2/ltc_usd/ticker').getContentText());
  var LTCUSD=(ticker['ticker']['buy']+ticker['ticker']['sell'])/2;
  
  // Pobierz wszystkie dane z arkusza
  var range = ss.getDataRange();
  var values = range.getValues();
  
  // Wczytaj poprzedni i aktualny kurs
  var prevLTCUSD=values[0][3];
  values[0][3]=LTCUSD;
  if (prevLTCUSD == null || prevLTCUSD == '' || prevLTCUSD == 0) {
    range.setValues(values);
    return;
  }
  
  // Sprawdź czy któryś poziom został aktywowany
  // Ten kod zabezpiecza przed kolejnymi wywołaniami alarmu, jesli kurs wahałby się wokół poziomu aktywacji alarmu
  var level, triggered, start, stop;
  // Jeśli notowania wzrosły sprawdź poziomy od dołu, jesli spadły od góry
  if (prevLTCUSD<LTCUSD) {start=1} else {start=values.length-1};
  for (var i=start;; )
  {
    // Aktualnie sprawdzany poziom i znacznik czy został aktywowany
    level=values[i][0]; triggered=values[i][1];
    // Jeśli poziom nie był aktywowany i został aktytowany...
    if (!triggered && ((prevLTCUSD<=level && LTCUSD>=level) || (prevLTCUSD>=level && LTCUSD<=level)) ) {
      // Nowy poziom aktywowany, wyczyść wszystkie poziomy z wyjątkiem aktualnego
      for (var j=1; j<values.length; j++) { values[j][1]=0;}; values[i][1]=1;
      // Zabituj datę ostatniej aktywacji
      values[i][2]=Utilities.formatDate(new Date(), "GMT+1", "yyyy-MM-dd HH:mm:ss");
      // Wyślij maila :)
      sendMail(level, LTCUSD);
    }
    // Jeśli notowania wzrosły sprawdź poziomy od dołu, jesli spadły od góry
    if (start==1) {
      i++;
      if (i>=values.length) break;
    } else {
      i--;
      if (i==1) break;
    }
  }
  range.setValues(values);
}

Oczywiście to tylko przykład. Trzeba pamiętać o skonfigurowaniu trigerra czasowego oraz podaniu adresu email. Skrypt można też sparametryzować tak aby adres (adresy?) email do powiadomień były definiowane w arkuszu, aby każdy adres mógł mieć inne poziomy aktywacji, aby można było definiować procentowe zmiany norowań itd. Pozostawiam to inwencji czytających :)

Leave a Reply