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.