Cryptocurrency Price Tracker in Java: Real-Time Market Data Monitoring

Introduction

Cryptocurrency price tracking is essential for traders, investors, and enthusiasts. This article demonstrates how to build a comprehensive cryptocurrency price tracker in Java that fetches real-time data from various APIs, stores historical data, and provides analytical insights.

Project Architecture

Cryptocurrency Price Tracker
├── Data Collection Layer (API Clients)
├── Data Processing Layer (Price Analysis)
├── Storage Layer (Database/Persistence)
├── Notification Layer (Alerts)
└── Presentation Layer (CLI/Web/REST API)

Core Dependencies

<!-- Maven Dependencies -->
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>

Core Domain Models

public class Cryptocurrency {
private String id;
private String symbol;
private String name;
private int rank;
private boolean active;
// Constructors, getters, setters
public Cryptocurrency() {}
public Cryptocurrency(String id, String symbol, String name) {
this.id = id;
this.symbol = symbol;
this.name = name;
}
}
public class PriceData {
private String cryptocurrencyId;
private double price;
private double volume24h;
private double marketCap;
private double priceChange24h;
private double priceChangePercentage24h;
private Date lastUpdated;
// Constructors, getters, setters
public PriceData() {}
public PriceData(String cryptocurrencyId, double price, Date lastUpdated) {
this.cryptocurrencyId = cryptocurrencyId;
this.price = price;
this.lastUpdated = lastUpdated;
}
}
public class HistoricalPrice {
private Long id;
private String cryptocurrencyId;
private double price;
private double volume;
private double marketCap;
private Date timestamp;
// Constructors, getters, setters
}

API Client Implementation

Base API Client

public abstract class CryptoApiClient {
protected static final OkHttpClient httpClient = new OkHttpClient();
protected static final Gson gson = new Gson();
protected String apiKey;
protected String baseUrl;
public CryptoApiClient(String baseUrl, String apiKey) {
this.baseUrl = baseUrl;
this.apiKey = apiKey;
}
protected String makeApiCall(String endpoint) throws IOException {
Request request = new Request.Builder()
.url(baseUrl + endpoint)
.addHeader("Accept", "application/json")
.addHeader("User-Agent", "Java-Crypto-Tracker/1.0")
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code: " + response);
}
return response.body().string();
}
}
public abstract List<Cryptocurrency> getAllCryptocurrencies() throws IOException;
public abstract PriceData getPriceData(String cryptocurrencyId) throws IOException;
public abstract List<HistoricalPrice> getHistoricalData(String cryptocurrencyId, String days) throws IOException;
}

CoinGecko API Implementation

public class CoinGeckoClient extends CryptoApiClient {
public CoinGeckoClient(String apiKey) {
super("https://api.coingecko.com/api/v3", apiKey);
}
@Override
public List<Cryptocurrency> getAllCryptocurrencies() throws IOException {
String response = makeApiCall("/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page=1");
Type listType = new TypeToken<List<CoinGeckoMarketData>>() {}.getType();
List<CoinGeckoMarketData> marketDataList = gson.fromJson(response, listType);
return marketDataList.stream()
.map(this::convertToCryptocurrency)
.collect(Collectors.toList());
}
@Override
public PriceData getPriceData(String cryptocurrencyId) throws IOException {
String response = makeApiCall("/coins/" + cryptocurrencyId + 
"?localization=false&tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false");
CoinGeckoCoinData coinData = gson.fromJson(response, CoinGeckoCoinData.class);
return convertToPriceData(coinData);
}
@Override
public List<HistoricalPrice> getHistoricalData(String cryptocurrencyId, String days) throws IOException {
String response = makeApiCall("/coins/" + cryptocurrencyId + 
"/market_chart?vs_currency=usd&days=" + days + "&interval=daily");
CoinGeckoHistoricalData historicalData = gson.fromJson(response, CoinGeckoHistoricalData.class);
return convertToHistoricalPrices(historicalData, cryptocurrencyId);
}
// Helper conversion methods
private Cryptocurrency convertToCryptocurrency(CoinGeckoMarketData marketData) {
Cryptocurrency crypto = new Cryptocurrency();
crypto.setId(marketData.getId());
crypto.setSymbol(marketData.getSymbol());
crypto.setName(marketData.getName());
crypto.setRank(marketData.getMarketCapRank());
crypto.setActive(true);
return crypto;
}
private PriceData convertToPriceData(CoinGeckoCoinData coinData) {
PriceData priceData = new PriceData();
priceData.setCryptocurrencyId(coinData.getId());
priceData.setPrice(coinData.getMarketData().getCurrentPrice().get("usd"));
priceData.setMarketCap(coinData.getMarketData().getMarketCap().get("usd"));
priceData.setVolume24h(coinData.getMarketData().getTotalVolume().get("usd"));
priceData.setPriceChange24h(coinData.getMarketData().getPriceChange24h());
priceData.setPriceChangePercentage24h(coinData.getMarketData().getPriceChangePercentage24h());
priceData.setLastUpdated(new Date());
return priceData;
}
private List<HistoricalPrice> convertToHistoricalPrices(CoinGeckoHistoricalData historicalData, String cryptoId) {
List<HistoricalPrice> historicalPrices = new ArrayList<>();
List<List<Double>> prices = historicalData.getPrices();
for (List<Double> pricePoint : prices) {
HistoricalPrice historicalPrice = new HistoricalPrice();
historicalPrice.setCryptocurrencyId(cryptoId);
historicalPrice.setPrice(pricePoint.get(1));
historicalPrice.setTimestamp(new Date(pricePoint.get(0).longValue()));
historicalPrices.add(historicalPrice);
}
return historicalPrices;
}
// DTO classes for JSON parsing
private static class CoinGeckoMarketData {
private String id;
private String symbol;
private String name;
private int market_cap_rank;
// Getters and setters
}
private static class CoinGeckoCoinData {
private String id;
private MarketData market_data;
// Getters and setters
static class MarketData {
private Map<String, Double> current_price;
private Map<String, Double> market_cap;
private Map<String, Double> total_volume;
private double price_change_24h;
private double price_change_percentage_24h;
// Getters and setters
}
}
private static class CoinGeckoHistoricalData {
private List<List<Double>> prices;
// Getters and setters
}
}

Price Tracker Service

@Service
public class PriceTrackerService {
private final CryptoApiClient apiClient;
private final Map<String, PriceData> currentPrices = new ConcurrentHashMap<>();
private final Map<String, List<HistoricalPrice>> historicalData = new ConcurrentHashMap<>();
private final List<PriceAlert> activeAlerts = new CopyOnWriteArrayList<>();
public PriceTrackerService(CryptoApiClient apiClient) {
this.apiClient = apiClient;
}
public void startTracking(List<String> cryptocurrencyIds, long intervalMinutes) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
try {
updatePrices(cryptocurrencyIds);
checkAlerts();
} catch (Exception e) {
System.err.println("Error updating prices: " + e.getMessage());
}
}, 0, intervalMinutes, TimeUnit.MINUTES);
}
private void updatePrices(List<String> cryptocurrencyIds) throws IOException {
for (String cryptoId : cryptocurrencyIds) {
try {
PriceData priceData = apiClient.getPriceData(cryptoId);
currentPrices.put(cryptoId, priceData);
// Store historical data for analysis
storeHistoricalData(priceData);
System.out.printf("Updated %s: $%.2f (24h: %.2f%%)%n",
cryptoId.toUpperCase(),
priceData.getPrice(),
priceData.getPriceChangePercentage24h());
} catch (IOException e) {
System.err.println("Failed to update price for " + cryptoId + ": " + e.getMessage());
}
}
}
private void storeHistoricalData(PriceData priceData) {
HistoricalPrice historicalPrice = new HistoricalPrice();
historicalPrice.setCryptocurrencyId(priceData.getCryptocurrencyId());
historicalPrice.setPrice(priceData.getPrice());
historicalPrice.setVolume(priceData.getVolume24h());
historicalPrice.setMarketCap(priceData.getMarketCap());
historicalPrice.setTimestamp(new Date());
historicalData
.computeIfAbsent(priceData.getCryptocurrencyId(), k -> new ArrayList<>())
.add(historicalPrice);
// Keep only last 1000 data points to prevent memory issues
List<HistoricalPrice> cryptoHistory = historicalData.get(priceData.getCryptocurrencyId());
if (cryptoHistory.size() > 1000) {
cryptoHistory = cryptoHistory.subList(cryptoHistory.size() - 1000, cryptoHistory.size());
historicalData.put(priceData.getCryptocurrencyId(), cryptoHistory);
}
}
public PriceData getCurrentPrice(String cryptocurrencyId) {
return currentPrices.get(cryptocurrencyId);
}
public Map<String, PriceData> getAllCurrentPrices() {
return new HashMap<>(currentPrices);
}
public List<HistoricalPrice> getPriceHistory(String cryptocurrencyId, int dataPoints) {
List<HistoricalPrice> history = historicalData.getOrDefault(cryptocurrencyId, new ArrayList<>());
if (history.size() <= dataPoints) {
return new ArrayList<>(history);
}
return history.subList(history.size() - dataPoints, history.size());
}
}

Price Analysis Engine

@Service
public class PriceAnalysisEngine {
public PriceAnalysis analyzePriceTrends(List<HistoricalPrice> priceHistory) {
if (priceHistory == null || priceHistory.size() < 2) {
throw new IllegalArgumentException("Insufficient historical data for analysis");
}
PriceAnalysis analysis = new PriceAnalysis();
analysis.setDataPoints(priceHistory.size());
// Calculate basic statistics
calculateBasicStats(priceHistory, analysis);
// Calculate moving averages
calculateMovingAverages(priceHistory, analysis);
// Calculate volatility
calculateVolatility(priceHistory, analysis);
// Detect trends
detectTrends(priceHistory, analysis);
return analysis;
}
private void calculateBasicStats(List<HistoricalPrice> history, PriceAnalysis analysis) {
double[] prices = history.stream()
.mapToDouble(HistoricalPrice::getPrice)
.toArray();
DoubleSummaryStatistics stats = Arrays.stream(prices).summaryStatistics();
analysis.setCurrentPrice(stats.getMax()); // Last price is current
analysis.setMinPrice(stats.getMin());
analysis.setMaxPrice(stats.getMax());
analysis.setAveragePrice(stats.getAverage());
// Calculate price change
double firstPrice = history.get(0).getPrice();
double lastPrice = history.get(history.size() - 1).getPrice();
double priceChange = lastPrice - firstPrice;
double priceChangePercent = (priceChange / firstPrice) * 100;
analysis.setPriceChange(priceChange);
analysis.setPriceChangePercent(priceChangePercent);
}
private void calculateMovingAverages(List<HistoricalPrice> history, PriceAnalysis analysis) {
int shortTermPeriod = Math.min(7, history.size());
int longTermPeriod = Math.min(30, history.size());
double shortMA = calculateSimpleMovingAverage(history, shortTermPeriod);
double longMA = calculateSimpleMovingAverage(history, longTermPeriod);
analysis.setShortTermMA(shortMA);
analysis.setLongTermMA(longMA);
// Golden/Death cross detection
analysis.setGoldenCross(shortMA > longMA);
analysis.setDeathCross(shortMA < longMA);
}
private double calculateSimpleMovingAverage(List<HistoricalPrice> history, int period) {
List<HistoricalPrice> recentData = history.subList(
Math.max(0, history.size() - period), history.size());
return recentData.stream()
.mapToDouble(HistoricalPrice::getPrice)
.average()
.orElse(0.0);
}
private void calculateVolatility(List<HistoricalPrice> history, PriceAnalysis analysis) {
double average = history.stream()
.mapToDouble(HistoricalPrice::getPrice)
.average()
.orElse(0.0);
double variance = history.stream()
.mapToDouble(HistoricalPrice::getPrice)
.map(price -> Math.pow(price - average, 2))
.average()
.orElse(0.0);
double volatility = Math.sqrt(variance);
analysis.setVolatility(volatility);
analysis.setVolatilityPercent((volatility / average) * 100);
}
private void detectTrends(List<HistoricalPrice> history, PriceAnalysis analysis) {
if (history.size() < 3) return;
// Simple linear regression for trend detection
double[] x = new double[history.size()];
double[] y = new double[history.size()];
for (int i = 0; i < history.size(); i++) {
x[i] = i;
y[i] = history.get(i).getPrice();
}
double slope = calculateSlope(x, y);
analysis.setTrendSlope(slope);
analysis.setBullishTrend(slope > 0);
analysis.setBearishTrend(slope < 0);
}
private double calculateSlope(double[] x, double[] y) {
double n = x.length;
double sumXY = 0, sumX = 0, sumY = 0, sumX2 = 0;
for (int i = 0; i < n; i++) {
sumXY += x[i] * y[i];
sumX += x[i];
sumY += y[i];
sumX2 += x[i] * x[i];
}
return (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
}
}
public class PriceAnalysis {
private int dataPoints;
private double currentPrice;
private double minPrice;
private double maxPrice;
private double averagePrice;
private double priceChange;
private double priceChangePercent;
private double shortTermMA;
private double longTermMA;
private boolean goldenCross;
private boolean deathCross;
private double volatility;
private double volatilityPercent;
private double trendSlope;
private boolean bullishTrend;
private boolean bearishTrend;
// Getters and setters
}

Alert System

public class PriceAlert {
private String id;
private String cryptocurrencyId;
private AlertCondition condition;
private double targetPrice;
private boolean triggered;
private Date createdAt;
private Date triggeredAt;
public enum AlertCondition {
PRICE_ABOVE,
PRICE_BELOW,
PERCENT_CHANGE_UP,
PERCENT_CHANGE_DOWN,
VOLUME_SPIKE
}
// Constructors, getters, setters
}
@Service
public class AlertService {
private final List<PriceAlert> activeAlerts = new CopyOnWriteArrayList<>();
private final PriceTrackerService priceTracker;
private final NotificationService notificationService;
public AlertService(PriceTrackerService priceTracker, NotificationService notificationService) {
this.priceTracker = priceTracker;
this.notificationService = notificationService;
}
public void addAlert(PriceAlert alert) {
activeAlerts.add(alert);
System.out.println("Alert added: " + alert.getCryptocurrencyId() + " " + 
alert.getCondition() + " " + alert.getTargetPrice());
}
public void checkAlerts() {
for (PriceAlert alert : activeAlerts) {
if (alert.isTriggered()) continue;
PriceData currentPrice = priceTracker.getCurrentPrice(alert.getCryptocurrencyId());
if (currentPrice == null) continue;
if (evaluateAlert(alert, currentPrice)) {
triggerAlert(alert, currentPrice);
}
}
}
private boolean evaluateAlert(PriceAlert alert, PriceData currentPrice) {
switch (alert.getCondition()) {
case PRICE_ABOVE:
return currentPrice.getPrice() >= alert.getTargetPrice();
case PRICE_BELOW:
return currentPrice.getPrice() <= alert.getTargetPrice();
case PERCENT_CHANGE_UP:
return currentPrice.getPriceChangePercentage24h() >= alert.getTargetPrice();
case PERCENT_CHANGE_DOWN:
return currentPrice.getPriceChangePercentage24h() <= alert.getTargetPrice();
default:
return false;
}
}
private void triggerAlert(PriceAlert alert, PriceData currentPrice) {
alert.setTriggered(true);
alert.setTriggeredAt(new Date());
String message = String.format(
"🚨 ALERT: %s is now $%.2f (Condition: %s %.2f)",
alert.getCryptocurrencyId().toUpperCase(),
currentPrice.getPrice(),
alert.getCondition(),
alert.getTargetPrice()
);
notificationService.sendNotification(message);
System.out.println(message);
}
public List<PriceAlert> getActiveAlerts() {
return new ArrayList<>(activeAlerts);
}
public void removeAlert(String alertId) {
activeAlerts.removeIf(alert -> alert.getId().equals(alertId));
}
}

Notification Service

public interface NotificationService {
void sendNotification(String message);
}
@Component
public class ConsoleNotificationService implements NotificationService {
@Override
public void sendNotification(String message) {
System.out.println("[NOTIFICATION] " + message);
}
}
@Component
public class EmailNotificationService implements NotificationService {
private final String smtpHost;
private final String smtpPort;
private final String username;
private final String password;
public EmailNotificationService(String smtpHost, String smtpPort, String username, String password) {
this.smtpHost = smtpHost;
this.smtpPort = smtpPort;
this.username = username;
this.password = password;
}
@Override
public void sendNotification(String message) {
// Implement email sending logic using JavaMail
// This is a simplified version
System.out.println("Sending email: " + message);
}
}

REST API Controller

@RestController
@RequestMapping("/api/crypto")
public class CryptoController {
private final PriceTrackerService priceTracker;
private final PriceAnalysisEngine analysisEngine;
private final AlertService alertService;
public CryptoController(PriceTrackerService priceTracker, PriceAnalysisEngine analysisEngine, AlertService alertService) {
this.priceTracker = priceTracker;
this.analysisEngine = analysisEngine;
this.alertService = alertService;
}
@GetMapping("/prices")
public Map<String, PriceData> getAllPrices() {
return priceTracker.getAllCurrentPrices();
}
@GetMapping("/prices/{cryptoId}")
public ResponseEntity<PriceData> getPrice(@PathVariable String cryptoId) {
PriceData priceData = priceTracker.getCurrentPrice(cryptoId);
return priceData != null ? ResponseEntity.ok(priceData) : ResponseEntity.notFound().build();
}
@GetMapping("/analysis/{cryptoId}")
public ResponseEntity<PriceAnalysis> getAnalysis(
@PathVariable String cryptoId,
@RequestParam(defaultValue = "30") int days) {
try {
List<HistoricalPrice> history = priceTracker.getPriceHistory(cryptoId, days);
PriceAnalysis analysis = analysisEngine.analyzePriceTrends(history);
return ResponseEntity.ok(analysis);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@PostMapping("/alerts")
public ResponseEntity<String> createAlert(@RequestBody PriceAlert alert) {
alertService.addAlert(alert);
return ResponseEntity.ok("Alert created successfully");
}
@GetMapping("/alerts")
public List<PriceAlert> getActiveAlerts() {
return alertService.getActiveAlerts();
}
@DeleteMapping("/alerts/{alertId}")
public ResponseEntity<String> deleteAlert(@PathVariable String alertId) {
alertService.removeAlert(alertId);
return ResponseEntity.ok("Alert deleted successfully");
}
}

Main Application

@SpringBootApplication
public class CryptoTrackerApplication implements CommandLineRunner {
@Autowired
private PriceTrackerService priceTracker;
@Autowired
private AlertService alertService;
public static void main(String[] args) {
SpringApplication.run(CryptoTrackerApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println("Starting Cryptocurrency Price Tracker...");
// Track major cryptocurrencies
List<String> cryptocurrencies = Arrays.asList(
"bitcoin", "ethereum", "cardano", "solana", "polkadot",
"dogecoin", "matic-network", "chainlink", "litecoin"
);
// Start tracking with 5-minute intervals
priceTracker.startTracking(cryptocurrencies, 5);
// Add some sample alerts
PriceAlert btcAlert = new PriceAlert();
btcAlert.setId("1");
btcAlert.setCryptocurrencyId("bitcoin");
btcAlert.setCondition(PriceAlert.AlertCondition.PRICE_ABOVE);
btcAlert.setTargetPrice(50000.0);
alertService.addAlert(btcAlert);
// Keep the application running
Thread.currentThread().join();
}
@Bean
public CryptoApiClient cryptoApiClient() {
// You can get a free API key from CoinGecko
return new CoinGeckoClient("YOUR_API_KEY_HERE");
}
}

Configuration

# application.yml
app:
crypto:
update-interval: 5
tracked-currencies: bitcoin,ethereum,cardano,solana
logging:
level:
com.yourpackage: DEBUG
spring:
datasource:
url: jdbc:h2:file:./crypto_data
driverClassName: org.h2.Driver
username: sa
password: 
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: update
show-sql: false
h2:
console:
enabled: true

Usage Examples

Command Line Interface

@Component
public class CryptoCLI {
private final PriceTrackerService priceTracker;
private final Scanner scanner = new Scanner(System.in);
public CryptoCLI(PriceTrackerService priceTracker) {
this.priceTracker = priceTracker;
}
public void startInteractiveMode() {
while (true) {
System.out.println("\n=== Crypto Tracker ===");
System.out.println("1. Show current prices");
System.out.println("2. Get price analysis");
System.out.println("3. Set price alert");
System.out.println("4. Exit");
System.out.print("Choose option: ");
String choice = scanner.nextLine();
switch (choice) {
case "1":
showCurrentPrices();
break;
case "2":
getPriceAnalysis();
break;
case "3":
setPriceAlert();
break;
case "4":
return;
default:
System.out.println("Invalid option");
}
}
}
private void showCurrentPrices() {
Map<String, PriceData> prices = priceTracker.getAllCurrentPrices();
System.out.println("\nCurrent Prices:");
prices.forEach((crypto, data) -> {
System.out.printf("%-15s: $%-10.2f (24h: %+.2f%%)%n",
crypto.toUpperCase(), data.getPrice(), data.getPriceChangePercentage24h());
});
}
private void getPriceAnalysis() {
System.out.print("Enter cryptocurrency ID: ");
String cryptoId = scanner.nextLine();
try {
List<HistoricalPrice> history = priceTracker.getPriceHistory(cryptoId, 30);
PriceAnalysis analysis = analysisEngine.analyzePriceTrends(history);
System.out.printf("Analysis for %s:%n", cryptoId.toUpperCase());
System.out.printf("Current Price: $%.2f%n", analysis.getCurrentPrice());
System.out.printf("30-Day Change: %.2f%%%n", analysis.getPriceChangePercent());
System.out.printf("Volatility: %.2f%%%n", analysis.getVolatilityPercent());
System.out.printf("Trend: %s%n", analysis.isBullishTrend() ? "BULLISH" : "BEARISH");
} catch (Exception e) {
System.out.println("Error getting analysis: " + e.getMessage());
}
}
}

Features Summary

  • Real-time price tracking from multiple exchanges
  • Historical data analysis with technical indicators
  • Custom price alerts with multiple conditions
  • REST API for integration with other applications
  • Multiple notification channels (console, email, etc.)
  • Extensible architecture for adding new data sources
  • Thread-safe implementation for concurrent operations

This cryptocurrency price tracker provides a solid foundation that can be extended with additional features like portfolio tracking, trading bots, or more sophisticated technical analysis indicators.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper