diff --git a/README.md b/README.md index de1767e..1ba3489 100644 --- a/README.md +++ b/README.md @@ -2,32 +2,32 @@ [![Known Vulnerabilities](https://snyk.io/test/github/ethauvin/cryptoprice/badge.svg?targetFile=pom.xml)](https://snyk.io/test/github/ethauvin/cryptoprice?targetFile=pom.xml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ethauvin_cryptoprice&metric=alert_status)](https://sonarcloud.io/dashboard?id=ethauvin_cryptoprice) [![GitHub CI](https://github.com/ethauvin/cryptoprice/actions/workflows/gradle.yml/badge.svg)](https://github.com/ethauvin/cryptoprice/actions/workflows/gradle.yml) [![CircleCI](https://circleci.com/gh/ethauvin/cryptoprice/tree/master.svg?style=shield)](https://circleci.com/gh/ethauvin/cryptoprice/tree/master) -# Retrieve cryptocurrencies current market prices +# Retrieve cryptocurrencies current prices A simple Kotlin/Java/Android implementation of the spot price [Coinbase Public API](https://developers.coinbase.com/api/v2#get-spot-price). ## Examples (TL;DR) ```kotlin -import net.thauvin.erik.crypto.CryptoPrice.Companion.marketPrice +import net.thauvin.erik.crypto.CryptoPrice.Companion.spotPrice // ... -val btc = marketPrice("BTC") // Bitcoin +val btc = spotPrice("BTC") // Bitcoin println(btc.amount) -val eth = marketPrice("ETH", "EUR") // Ethereum in Euros +val eth = spotPrice("ETH", "EUR") // Ethereum in Euros println(eth.amount) ``` - View [Kotlin](https://github.com/ethauvin/cryptoprice/blob/master/examples/src/main/kotlin/com/example/CryptoPriceExample.kt) or [Java](https://github.com/ethauvin/cryptoprice/blob/master/examples/src/main/java/com/example/CryptoPriceSample.java) Examples. -### Market Price +### Spot Price -The `marketPrice` function defines the following parameters: +The `spotPrice` function defines the following parameters: ```kotlin -marketPrice( +spotPrice( base: String, // Required currency: String = "USD", date: LocalDate? = null, @@ -43,14 +43,14 @@ Parameters | Description A `CryptoPrice` is returned defined as follows: ```kotlin -CryptoPrice(val base: String, val currency: String, val amount: Double) +CryptoPrice(val base: String, val currency: String, val amount: BigDecimal) ``` The parameter names match the [Coinbase API](https://developers.coinbase.com/api/v2#get-spot-price). To display the amount as a fomatted currency use the `toCurrency` function: ```kotlin -val price = CryptoPrice("BTC", "EUR", 12345.67) +val price = CryptoPrice("BTC", "EUR", 12345.67.toBigDecimal()) println(price.toCurrency()) // will print €12,345.67 ``` ### Extending diff --git a/build.gradle.kts b/build.gradle.kts index f87cdb0..5f6febb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ plugins { defaultTasks(ApplicationPlugin.TASK_RUN_NAME) -description = "Retrieve cryptocurrencies current market prices." +description = "Retrieve cryptocurrencies current prices." group = "net.thauvin.erik" version = "0.9.0-SNAPSHOT" diff --git a/examples/src/main/java/com/example/CryptoPriceSample.java b/examples/src/main/java/com/example/CryptoPriceSample.java index 2a533a8..1632b27 100644 --- a/examples/src/main/java/com/example/CryptoPriceSample.java +++ b/examples/src/main/java/com/example/CryptoPriceSample.java @@ -10,18 +10,18 @@ import java.util.List; public class CryptoPriceSample { public static void main(String[] args) { try { - // Get current Bitcoin market price. - final CryptoPrice price = CryptoPrice.marketPrice("BTC"); + // Get current Bitcoin spot price. + final CryptoPrice price = CryptoPrice.spotPrice("BTC"); System.out.println("The current Bitcoin price is " + price.toCurrency()); - // Get current Bitcoin market price in Euros. - final CryptoPrice euroPrice = CryptoPrice.marketPrice("BTC", "EUR"); + // Get current Bitcoin spot price in Euros. + final CryptoPrice euroPrice = CryptoPrice.spotPrice("BTC", "EUR"); System.out.println("The current Bitcoin price is " + euroPrice.toCurrency()); System.out.println(); - // Get current Ethereum market price in Pound sterling. - final CryptoPrice gbpPrice = CryptoPrice.marketPrice("ETH", "GBP"); + // Get current Ethereum spot price in Pound sterling. + final CryptoPrice gbpPrice = CryptoPrice.spotPrice("ETH", "GBP"); System.out.println("The current Ethereum price is " + gbpPrice.toCurrency()); System.out.println(); diff --git a/examples/src/main/kotlin/com/example/CryptoPriceExample.kt b/examples/src/main/kotlin/com/example/CryptoPriceExample.kt index 53130db..0fdda95 100644 --- a/examples/src/main/kotlin/com/example/CryptoPriceExample.kt +++ b/examples/src/main/kotlin/com/example/CryptoPriceExample.kt @@ -1,22 +1,22 @@ package com.example import net.thauvin.erik.crypto.CryptoPrice.Companion.apiCall -import net.thauvin.erik.crypto.CryptoPrice.Companion.marketPrice +import net.thauvin.erik.crypto.CryptoPrice.Companion.spotPrice import net.thauvin.erik.crypto.CryptoPrice.Companion.toPrice fun main(@Suppress("UNUSED_PARAMETER") args: Array) { - // Get current Bitcoin market price. - val price = marketPrice("BTC") + // Get current Bitcoin spot price. + val price = spotPrice("BTC") println("The current Bitcoin price is ${price.toCurrency()}") - // Get current Bitcoin market price in Euro. - val euroPrice = marketPrice("BTC", "EUR") + // Get current Bitcoin spot price in Euro. + val euroPrice = spotPrice("BTC", "EUR") println("The current Bitcoin price is ${euroPrice.toCurrency()}") println() - // Get current Ethereum market price in Pound sterling. - val gbpPrice = marketPrice("ETH", "GBP") + // Get current Ethereum spot price in Pound sterling. + val gbpPrice = spotPrice("ETH", "GBP") println("The current Ehtereum price is ${gbpPrice.toCurrency()}") println() diff --git a/pom.xml b/pom.xml index de8493c..b893da8 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ cryptoprice 0.9.0-SNAPSHOT cryptoprice - Retrieve cryptocurrencies current market prices. + Retrieve cryptocurrencies current prices. https://github.com/ethauvin/cryptoprice diff --git a/src/main/kotlin/net/thauvin/erik/crypto/CryptoPrice.kt b/src/main/kotlin/net/thauvin/erik/crypto/CryptoPrice.kt index 5f9c4f5..7248ff7 100644 --- a/src/main/kotlin/net/thauvin/erik/crypto/CryptoPrice.kt +++ b/src/main/kotlin/net/thauvin/erik/crypto/CryptoPrice.kt @@ -38,17 +38,18 @@ import okhttp3.Request import org.json.JSONException import org.json.JSONObject import java.io.IOException +import java.math.BigDecimal import java.text.NumberFormat import java.time.LocalDate import java.util.Currency import java.util.Locale /** - * A small Kotlin/Java library for retrieving cryptocurrencies current market prices. + * A small Kotlin/Java library for retrieving cryptocurrencies current spot prices. * * @author [Erik C. Thauvin](https://erik.thauvin.net/) */ -open class CryptoPrice(val base: String, val currency: String, val amount: Double) { +open class CryptoPrice(val base: String, val currency: String, val amount: BigDecimal) { companion object { // Coinbase API URL private const val COINBASE_API_URL = "https://api.coinbase.com/v2/" @@ -63,7 +64,7 @@ open class CryptoPrice(val base: String, val currency: String, val amount: Doubl val json = JSONObject(this) if (json.has("data")) { with(json.getJSONObject("data")) { - return CryptoPrice(getString("base"), getString("currency"), getString("amount").toDouble()) + return CryptoPrice(getString("base"), getString("currency"), getString("amount").toBigDecimal()) } } else { throw CryptoException(message = "Missing price data.") @@ -115,14 +116,14 @@ open class CryptoPrice(val base: String, val currency: String, val amount: Doubl @JvmStatic fun main(args: Array) { args.forEach { - with(marketPrice(it)) { + with(spotPrice(it)) { println("$base:\t" + "%10s".format(toCurrency())) } } } /** - * Retrieve the current market price. + * Retrieve the current spot price. * * @param base The cryptocurrency ticker symbol. (`BTC`, `ETH`, `ETH2`, etc.) * @param currency The fiat currency ISO 4217 code. (`USD`, `GPB`, `EUR`, etc.) @@ -131,7 +132,7 @@ open class CryptoPrice(val base: String, val currency: String, val amount: Doubl @JvmStatic @JvmOverloads @Throws(CryptoException::class, IOException::class) - fun marketPrice(base: String, currency: String = "USD", date: LocalDate? = null): CryptoPrice { + fun spotPrice(base: String, currency: String = "USD", date: LocalDate? = null): CryptoPrice { val params = if (date != null) mapOf("date" to "$date") else emptyMap() return apiCall(listOf("prices", "$base-$currency", "spot"), params).toPrice() } @@ -145,6 +146,7 @@ open class CryptoPrice(val base: String, val currency: String, val amount: Doubl fun toCurrency(locale: Locale = Locale.getDefault(Locale.Category.FORMAT)): String { return NumberFormat.getCurrencyInstance(locale).let { it.setCurrency(Currency.getInstance(currency)) + it.setMinimumFractionDigits(2) it.format(amount) } } diff --git a/src/test/kotlin/net/thauvin/erik/crypto/CryptoPriceTest.kt b/src/test/kotlin/net/thauvin/erik/crypto/CryptoPriceTest.kt index 7a9a23e..b41fae2 100644 --- a/src/test/kotlin/net/thauvin/erik/crypto/CryptoPriceTest.kt +++ b/src/test/kotlin/net/thauvin/erik/crypto/CryptoPriceTest.kt @@ -1,8 +1,9 @@ package net.thauvin.erik.crypto import net.thauvin.erik.crypto.CryptoPrice.Companion.apiCall -import net.thauvin.erik.crypto.CryptoPrice.Companion.marketPrice +import net.thauvin.erik.crypto.CryptoPrice.Companion.spotPrice import net.thauvin.erik.crypto.CryptoPrice.Companion.toPrice +import java.math.BigDecimal import java.time.LocalDate import java.util.Locale import kotlin.test.Test @@ -17,37 +18,37 @@ class CryptoPriceTest { @Test @Throws(CryptoException::class) fun testBTCPrice() { - val price = marketPrice("BTC") + val price = spotPrice("BTC") assertEquals("BTC", price.base, "BTC") assertEquals("USD", price.currency, "is USD") - assertTrue(price.amount > 0.00, "BTC > 0") + assertTrue(price.amount.signum() > 0, "BTC > 0") } @Test @Throws(CryptoException::class) fun testETHPrice() { - val price = marketPrice("ETH", "EUR") + val price = spotPrice("ETH", "EUR") assertEquals("ETH", price.base, "ETH") assertEquals("EUR", price.currency, "is EUR") - assertTrue(price.amount > 0.00, "ETH > 0") + assertTrue(price.amount.signum() > 0, "ETH > 0") } @Test @Throws(CryptoException::class) fun testETH2Price() { - val price = marketPrice("ETH2", "GBP") + val price = spotPrice("ETH2", "GBP") assertEquals("ETH2", price.base, "ETH2") assertEquals("GBP", price.currency, "is GBP") - assertTrue(price.amount > 0.00, "GBP > 0") + assertTrue(price.amount.signum() > 0, "GBP > 0") } @Test @Throws(CryptoException::class) fun testBCHPrice() { - val price = marketPrice("BCH", "GBP", LocalDate.now().minusDays(1)) + val price = spotPrice("BCH", "GBP", LocalDate.now().minusDays(1)) assertEquals("BCH", price.base, "BCH") assertEquals("GBP", price.currency, "is GBP") - assertTrue(price.amount > 0.00, "BCH > 0") + assertTrue(price.amount.signum() > 0, "BCH > 0") } @Test @@ -56,26 +57,26 @@ class CryptoPriceTest { val price = apiCall(listOf("prices", "BTC-USD", "buy"), emptyMap()).toPrice() assertEquals("BTC", price.base, "buy BTC") assertEquals("USD", price.currency, "buy BTC is USD") - assertTrue(price.amount > 0.00, "buy BTC > 0") + assertTrue(price.amount.signum() > 0, "buy BTC > 0") } @Test @Throws(CryptoException::class) - fun testMarketPriceExceptions() { + fun testSpotPrice() { assertFailsWith( message = "FOO did not fail", exceptionClass = CryptoException::class, - block = { marketPrice("FOO") } + block = { spotPrice("FOO") } ) assertFailsWith( message = "BAR did not fail", exceptionClass = CryptoException::class, - block = { marketPrice("BTC", "BAR") } + block = { spotPrice("BTC", "BAR") } ) try { - marketPrice("FOOBAR") + spotPrice("FOOBAR") } catch (e: CryptoException) { assertTrue(e.statusCode != 400, "FOOBAR status code is not 400") } @@ -84,7 +85,7 @@ class CryptoPriceTest { @Test @Throws(IllegalArgumentException::class) fun testToCurrency() { - val d = 12345.67 + val d = 12345.67.toBigDecimal() val usd = CryptoPrice("BTC", "USD", d) assertEquals("$12,345.67", usd.toCurrency(), "EUR format") @@ -101,18 +102,18 @@ class CryptoPriceTest { assertEquals("12 345,67 €", fr.toCurrency(Locale.FRANCE), "EUR-FR format") val jp = CryptoPrice("BTC", "JPY", d) - assertEquals("¥12,346", jp.toCurrency(Locale.JAPAN), "EUR-JPY format") + assertEquals("¥12,345.67", jp.toCurrency(Locale.JAPAN), "EUR-JPY format") } @Test @Throws(CryptoException::class) fun testToPrice() { - val d = 57515.69 + val d = "57515.69" val json = "{\"data\":{\"base\":\"BTC\",\"currency\":\"USD\",\"amount\":\"$d\"}}" val price = json.toPrice() assertEquals("BTC", price.base, "base is BTC") assertEquals("USD", price.currency, "currency is USD") - assertEquals(d, price.amount, "amount is 57515.69") + assertEquals(d, price.amount.toString(), "amount is 57515.69") assertFailsWith( message = "double conversion did not fail",