Перевод статьи:
https://medium.com/swlh/building-a-bitcoin-price-watcher-with-alerts-in-java-d52824e0631eСоздаем на Java Наблюдателя за Ценой Биткоина с УведомлениямиВы будете автоматически уведомлены об изменениях стоимости Биткоина.Как разработчик вы, возможно, интересуетесь всем, что связано с технологиями. Поэтому вы, должно быть, уже слышали о Биткоине. Даже будет лучше, если я представлю, что вы уже проявляли интерес к тому, как это работает, и конкретно к технологии Блокчейн. Если вы когда-либо интересовались Биткоином, вы могли заметить, что его цена склонна к значительным колебаниям.
Волатильность Биткоина можно объяснить по большей части тем, что это новый для рынка продукт. Если вы решите купить Биткоин, вы, наверное, потратите какое-то время, наблюдая за его ценой на биржевых платформах. Это может отнять у вас невероятно много времени.
Вместо того, чтобы тратить долгие часы на наблюдения за ценой Биткоина, я научу вас, как на Java написать программу для наблюдения за ценой Биткоина, и получать оповещения непосредственно на вашем рабочем столе, когда цена достигнет заданного вами уровня.
. . .
Технические характеристики Наблюдателя Цены Биткоина (Bitcoin Price Watcher или BPW)Программа BPW, которую мы будем создавать в этой статье, будет иметь следующие функции:
- Получение цены Биткоина, используя Индекс Цены Биткоина (Bitcoin Price Index или BPI) с сайта CoinDesk.
- Отслеживание цены Биткоина каждую минуту.
- Отображение цен на Биткоин для просмотра в интерактивном режиме.
- Предоставление списка всех отслеженных цен.
- Показ уведомлений непосредственно на рабочем столе пользователя.
Прочитав эти функции, вы поняли, что наша программа будет запущена на компьютере конечного пользователя. Она будет запущена в командной строке терминала пользователем, который сможет добавлять новое значение цены для отслеживания, через ввод команды, такой как эта:
WATCH;7450
В этом случае пользователь запрашивает выдать уведомление, когда Биткоин достигнет цены 7 450 долларов.
. . .
Создание Проекта JavaПервым шагом будет создание проекта Java, который будет использовать Maven в качестве менеджера зависимостей. Так как мы вынуждены будем обращаться к веб-сервису CoinDesk, мы добавим библиотеку OkHttp в качестве зависимости в наш проект.
OkHttp - это эффективный HTTP-клиент, который идеально подойдет для нашего проекта.Поскольку веб-сервис BPI CoinDesk возвращает свои данные в формате JSON, мы будем вынуждены добавить в зависимость библиотеку для парсинга данных, полученных таким образом.
Для этого я буду использовать библиотеку org.json, которая занимает мало места и легка в использовании. Все это дает нам следующий файл POM для нашего проекта:
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ssaurel</groupId>
<artifactId>BitcoinPriceAlert</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BitcoinPriceAlert</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20190722</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
github.com. . .
Получение Цены БиткоинаКак только проект будет создан и добавлены зависимости, мы переходим к получению цены Биткоина. Веб-сервис Bitcoin Price Index (BPI) CoinDesk доступен по следующему адресу:
https://api.coindesk.com/v1/bpi/currentprice.jsonПервый вызов этого API позволит нам увидеть, в каком виде он отображается:
JSON, возвращаемый сервисом BPI
На уровне главного класса нашей программы мы создаем свойство
client типа
OkHttpClient. Этот объект является точкой входа в OkHttp API и затем позволит нам запустить наш запрос к сервису.
Запрос представлен объектом
Request, в котором мы указали конечной точкой BPI CoinDesk. Последний хранится в статической переменной
BITCOIN_PRICE_ENDPOINT.
Чтобы выполнить этот запрос с заранее созданным экземпляром
OkHttpClient, мы используем его метод
newCall, в который мы передаем объект
Request, который мы только что создали, в качестве входных данных. Затем остается вызвать метод
enqueue, принимающий в качестве входных параметров объект типа
Callback. Использование этого интерфейса позволяет нам разделить обработку результата запроса и фактический вызов.
Все это дает следующий код метода
loadBitcoinPrice:
private void loadBitcoinPrice(Callback callback) {
Request request = new Request.Builder().url(BITCOIN_PRICE_ENDPOINT).build();
client.newCall(request).enqueue(callback);
}
Так как наша программа должна постоянно мониторить цену Биткоина, необходимо, чтобы этот вызов Сервиса BPI выполнялся через регулярные промежутки времени.
Для реализации этой операции Java предлагает Timer API. Поэтому я определю объект
TimerTask, внутри которого задам обращение к методу
loadBitcoinPrice. Выполнение данного
TimerTask запланировано каждую минуту после вызова метода
schedule созданного для данного экземпляра Timer.
Поэтому мы имеем следующий код:
private void launchTimer() {
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
loadBitcoinPrice(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
String str = response.body().string();
parseBitcoinPrice(str);
}
@Override
public void onFailure(Call call, IOException ioe) { }
});
}
}, 0, PERIOD);
}
private void cancelTimer() {
if (timer != null) {
timer.cancel();
}
}
private void loadBitcoinPrice(Callback callback) {
Request request = new Request.Builder().url(BITCOIN_PRICE_ENDPOINT).build();
client.newCall(request).enqueue(callback);
}
github.com. . .
Парсинг Данных Полученного JSONВ коде выше вы, должно быть, заметили наличие метода
parseBitcoinPrice, принимающего в качестве входных данных результат вызова сервиса Bitcoin Price Index. Этот метод и будет отвечать за анализ данных JSON, полученных после вызова Сервиса, чтобы получить текущую цену биткойна.
В этом методе я начинаю создавать экземпляр
JSONObject из данных, переданных в качестве входных. Затем я сделаю два вызова, связанных с методом
getJSONObject соответственно с параметрами "bpi", а затем "USD“, прежде чем, наконец, вызвать метод
getFloat с входным ”rate_float".
Это даст мне текущую стоимость Биткоина в долларах США.Я вывожу в консоли дату и время, а также цену Биткойна, которая только что была получена. Последняя известная цена биткойна также хранится в свойстве
currentPrice нашего главного класса.
На этом моменте метод
parseBitcoinPrice выглядит следующим образом:
private void parseBitcoinPrice(String str) {
JSONObject jsonObject = new JSONObject(str);
currentPrice = jsonObject.getJSONObject("bpi").getJSONObject("USD").getFloat("rate_float");
System.out.println(LocalDateTime.now() + " | Current price = " + currentPrice + "\n");
// ...
}
Я заостряю внимание на этом моменте, потому что, как только эта цена будет получена, необходимо будет сравнить ее с ценами, заданными пользователем для мониторинга.
. . .
Моделирование цены для мониторингаПрежде чем я смогу сравнить текущую полученную стоимость Биткоина с ценами, определенными пользователем для мониторинга, мне нужно будет смоделировать цену для мониторинга.
Цена для мониторинга моделируется в классе
Price и имеет следующие два свойства:
- Свойство target типа float отображает цену для мониторинга.
- Свойство type, которое является экземпляром Type типа enum (перечисление), отражает типы выполняемых наблюдений.
В самом деле, когда пользователь запрашивает мониторить заданную стоимость, мы будем определять, стоит ли выполнить мониторинг с большей или с меньшей стороны, чтобы сделать уведомление более релевантным.
Перечисление
Type которое создано в соответствии с этими требованиями, имеет два значения:
UP и
DOWN. Кроме того, мы определяем два абстрактных метода:
- Метод reached, принимающий в качестве входных данных текущую цену биткойна и цену для мониторинга и возвращающий значение true, если эта цена достигнута.
- Метод msg, принимающий в качестве входных данных текущую цену биткойна и цену для мониторинга и возвращающий сообщение для отображения пользователю.
Реализация этих методов после выполняется специфическим образом для каждого из двух значений перечисления
Type. Наконец, определение типа цены для мониторинга выполняется при создании объекта
Price путем сравнения текущей стоимости биткойна с ценой для мониторинга, определенной пользователем.
Это дает следующий код для класса
Price:
public class Price {
enum Type {
UP() {
@Override
public boolean reached(float current, float target) {
return target < current;
}
@Override
public String msg(float current, float target) {
return "BTC has rised beyond " + target + " with price : " + current;
}
},
DOWN {
@Override
public boolean reached(float current, float target) {
return current < target;
}
@Override
public String msg(float current, float target) {
return "BTC has fallen below " + target + " with price : " + current;
}
};
public abstract boolean reached(float current, float target);
public abstract String msg(float current, float target);
}
public float target;
public Type type;
public Price(float current, float target) {
this.target = target;
type = Float.compare(current, target) < 0 ? Type.UP : Type.DOWN;
}
}
github.com. . .
Оповещение Пользователя о Достижении Заданной ЦеныРанее я говорил вам, что метод
parseBitcoinPrice еще не завершен. На данный момент он отображает только цену биткойна, полученную из сервиса Bitcoin Price Index.
Для того, чтобы работа этого метода была завершена, он должен иметь возможность перебирать цены, которые отслеживает пользователь, и для каждого достижения цены отправлять пользователю предупреждение. Это позволит ему сразу быть предупрежденным о достижении цены, которую он хотел видеть.
Кроме того, как только искомая цена достигнута, не следует забывать удалить ее из списка цен для мониторинга.
Для того, чтобы сделать это, я создам свойство
pricesToWatch в главном классе моей программы. Это свойство будет содержать список объектов
Price. Для того, чтобы перебирать этот список или иметь возможность при необходимости удалять пункты, я буду использовать объект
Iterator.
Затем для каждой цены я получу свое свойство
type, на котором я мог бы вызвать метод
reached, передав в качестве входных данных текущую цену Биткоина, и искомую цену, содержащуюся в текущем объекте
Price.
Если метод reached возвращает значание true, это означает, что искомая пользователем цена достигнута.Затем мы должны получить сообщение для отображения пользователю, вызвав метод
msg свойства
type, связанный с текущим объектом
Price. Затем определить метод
displayNotification, который будет отвечать за запуск системного уведомления на принимающей машине пользователя.
Виртуальная машина Java для этого предлагает
SystemTray API. Который поддерживает правильное отображение системных уведомлений независимо от операционной системы, на которой будет работать программа.
Наконец, остается удалить цену из списка отслеживаемых, вызвав метод итератора
remove. Это дает нам следующий код:
private void parseBitcoinPrice(String str) {
JSONObject jsonObject = new JSONObject(str);
currentPrice = jsonObject.getJSONObject("bpi").getJSONObject("USD").getFloat("rate_float");
System.out.println(LocalDateTime.now() + " | Current price = " + currentPrice + "\n");
for (Iterator<Price> it = pricesToWatch.iterator(); it.hasNext();) {
Price priceToWatch = it.next();
if (priceToWatch.type.reached(currentPrice, priceToWatch.target)) {
String message = priceToWatch.type.msg(currentPrice, priceToWatch.target);
System.out.println(message);
displayNotification("Bitcoin Watcher", message);
// remove from list to watch
it.remove();
}
}
}
public void displayNotification(String title, String message) {
if (SystemTray.isSupported()) {
//Obtain only one instance of the SystemTray object
SystemTray tray = SystemTray.getSystemTray();
Image image = Toolkit.getDefaultToolkit().createImage("icon.png");
TrayIcon trayIcon = new TrayIcon(image, "Bitcoin Watcher Notif");
trayIcon.setImageAutoSize(true);
trayIcon.setToolTip("Bitcoin Watcher");
try {
tray.add(trayIcon);
} catch (AWTException e) {}
trayIcon.displayMessage(title, message, MessageType.INFO);
} else {
System.err.println("System tray not supported!");
}
}
github.com. . .