diff --git a/README.md b/README.md index bbb968a..915214e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ A marketmaker bot based on Broxus Nova. *huckster* helps you to place asynchronous orders in Broxus Nova orderbook using different strategies and price feeds. ## Building - * Clone *huckster* repository with the `git clone` command. * Go to the repo directory * Run `./gradlew build` to build the executable @@ -14,6 +13,8 @@ A marketmaker bot based on Broxus Nova. mv ./build/libs/hucksterFat.jar ./huckster.jar ``` +**Important:** You need to have `jdk1.8.0_201` installed to build the app. In case it is not the only Java instance on your PC, specify the path to jdk explicitly by adding `-D org.gradle.java.home=` parameter to Gradlew. + ## Running To run *huckster*, use a regular java syntax: @@ -22,13 +23,22 @@ java -jar ./huckster.jar [JOB] [PARAMS] ``` ### Supported jobs -* `seller` - Sell at the highest possible price +* `seller`
Sell at the highest possible price +* `orderbook`
Visualize existing orderbook ### Runtime parameters +#### `seller` job * `-k, --keys `
Keys to access Broxus Nova platform * `-s, --strategy `
Orders placement strategy configuration * `-pad, --priceAdapter `
Price adapter configuration -* `-pau, --priceAuth `
Price adapter authentication file \[optional\] +* `-pau, --priceAuth `
Price adapter authentication file \[optional, adapter-dependent\] + +#### `orderbook` job +* `-p, --pair `
+ Base and counter currency tickers separated by a delimiter. + Supported delimiters: underscore(`_`), dash(`-`) and slash (`/`) +* `-k, --keys `
Keys to access Broxus Nova platform +* `-r, --refresh `
Orderbook refresh rate in seconds \[optional\]. Default: 10 ### Supported strategies * `simple`
@@ -47,6 +57,6 @@ See [examples](examples) folder for samples of strategy configuration files. Goes without saying, fixed prices per different target currencies. * `googleSheet`
- Sources price feed from a Google Sheet. Requires OAuth 2.0 file downloaded from Google Developer Account. + Sources price feed from a Google Sheet. Requires [OAuth 2.0 file](https://developers.google.com/identity/protocols/oauth2) downloaded from [Google Developer Account](https://console.developers.google.com/). See [examples](examples) folder for samples of price feed configuration files. diff --git a/src/main/kotlin/com/broxus/huckster/OrdersQueue.kt b/src/main/kotlin/com/broxus/huckster/OrdersQueue.kt index 71855ab..a198bf4 100644 --- a/src/main/kotlin/com/broxus/huckster/OrdersQueue.kt +++ b/src/main/kotlin/com/broxus/huckster/OrdersQueue.kt @@ -33,7 +33,8 @@ object OrdersQueue: OrdersQueue { event.fromCurrency, event.toCurrency, event.fromAmount, - event.toAmount + event.toAmount, + "Huckster MM" )?.let { logger2( @@ -92,7 +93,7 @@ object OrdersQueue: OrdersQueue { */ override fun flush( userAddress: String, - addressType: AddressType, + addressType: String, workspaceId: String?, currency: String? ): Boolean { @@ -116,9 +117,9 @@ object OrdersQueue: OrdersQueue { return true } - override suspend fun drawOrderBook(base: String, counter: String, refreshInterval: Long) { + override suspend fun drawOrderBook(base: String, counter: String, refreshInterval: Int) { var orderBook: ExchangeOrderBook? - var averagePrice: Float = 0.0F + var averagePrice: Float var priceMaxLength: Int = 0 var volumeMaxLength: Int = 0 var minAskPrice: Float @@ -136,12 +137,12 @@ object OrdersQueue: OrdersQueue { ).apply { roundingMode = RoundingMode.HALF_UP } - var outMessage: String = "" + var outMessage: String val minScale = 1.0F val maxScale = 11.0F var interval: Float var bars: Int - var t: String = "" + var t: String while(true) { orderBook = api!!.getOrderBook(base, counter) @@ -190,7 +191,7 @@ object OrdersQueue: OrdersQueue { //println("\u001Bc") priceColumnWidth = max(priceMaxLength, 5) + 2 - volumeColumnWidth = 11 + volumeMaxLength + 3 + volumeColumnWidth = 11 + volumeMaxLength + 4 interval = (maxVolume - minVolume) / (maxScale - minScale) @@ -205,7 +206,8 @@ object OrdersQueue: OrdersQueue { bars = ((ask.volume.toFloat() - minVolume) / interval).toInt() + 1 outMessage += "│ " + ask.rate + " ".repeat(priceColumnWidth - ask.rate.length - 1) + "│ " + - "█".repeat(bars).red() + " " + ask.volume.red() + " ".repeat(volumeColumnWidth - bars - ask.volume.length - 2) + "│\n" + " ".repeat(volumeMaxLength - ask.volume.length) + ask.volume.red() + + " ".repeat(volumeColumnWidth - bars - volumeMaxLength - 2) + "█".repeat(bars).red() + " " + "│\n" } if(it.asks.count() == 0) { @@ -225,7 +227,8 @@ object OrdersQueue: OrdersQueue { bars = ((bid.volume.toFloat() - minVolume) / interval).toInt() + 1 outMessage += "│ " + bid.rate + " ".repeat(priceColumnWidth - bid.rate.length - 1) + "│ " + - "█".repeat(bars).green() + " " + bid.volume.green() + " ".repeat(volumeColumnWidth - bars - bid.volume.length - 2) + "│\n" + " ".repeat(volumeMaxLength - bid.volume.length) + bid.volume.red() + + " ".repeat(volumeColumnWidth - bars - volumeMaxLength - 2) + "█".repeat(bars).red() + " " + "│\n" } if(it.bids.count() == 0) { @@ -238,7 +241,7 @@ object OrdersQueue: OrdersQueue { print(outMessage) - delay(refreshInterval * 1000) + delay(refreshInterval * 1000L) } } } diff --git a/src/main/kotlin/com/broxus/huckster/interfaces/OrdersQueue.kt b/src/main/kotlin/com/broxus/huckster/interfaces/OrdersQueue.kt index 9145ed2..240724a 100644 --- a/src/main/kotlin/com/broxus/huckster/interfaces/OrdersQueue.kt +++ b/src/main/kotlin/com/broxus/huckster/interfaces/OrdersQueue.kt @@ -31,10 +31,10 @@ interface OrdersQueue: CoroutineScope { */ fun flush( userAddress: String, - addressType: AddressType, + addressType: String, workspaceId: String?, currency: String? ): Boolean - suspend fun drawOrderBook(base: String, counter: String, refreshInterval: Long) + suspend fun drawOrderBook(base: String, counter: String, refreshInterval: Int) } \ No newline at end of file diff --git a/src/main/kotlin/com/broxus/huckster/main.kt b/src/main/kotlin/com/broxus/huckster/main.kt index ed34c09..6babb49 100644 --- a/src/main/kotlin/com/broxus/huckster/main.kt +++ b/src/main/kotlin/com/broxus/huckster/main.kt @@ -28,6 +28,8 @@ fun main(args: Array) { var priceAuthPath = "" var base = "" var counter = "" + var refreshInterval = 30 + val version = "0.3 rev.1" val greeting = " \n" + @@ -40,6 +42,7 @@ fun main(args: Array) { " ".yellow() val usage = + "huckster v.$version\n\n".bold() + "USAGE:\n".brightGreen() + "\thuckster " + " ".green() + "[PARAMS]\n".yellow() + "\t\n" + @@ -56,12 +59,16 @@ fun main(args: Array) { "\t\tStrategy configuration\n" + "\t\t-pad, --priceAdapter ".italic() + "\tPrice adapter configuration\n" + - "\t\t-pau, --priceAuth ".italic() + - "\tPrice adapter authentication file [optional]\n\n" + + "\t\t[-pau, --priceAuth ]".italic() + + "\tPrice adapter authentication file [adapter-dependent]\n\n" + "\tOrderbook:\n".underline() + "\t\t-p, --pair ".italic() + "\t\t\tBase and counter currency tickers separated by a delimiter.\n" + - "\t\t\t\t\t\t\t\t\tSupported delimiters: underscore(_), dash(-) and slash (/)" + "\t\t\t\t\t\t\t\t\tSupported delimiters: underscore(_), dash(-) and slash (/)\n" + + "\t\t-k, --keys ".italic() + + "\t\t\tKeys to access Broxus Nova platform\n" + + "\t\t[-r, --refresh ]".italic() + + "\tOrderbook refresh rate in seconds. Default: 10" val errors: MutableMap> = mutableMapOf() @@ -151,6 +158,23 @@ fun main(args: Array) { } } + "--refresh", "-r" -> { + if(i < args.lastIndex) { + args[i + 1].toIntOrNull().let { + when(it) { + null -> { + if(!errors.containsKey("wrong_orderbook")) errors["wrong_orderbook"] = mutableListOf() + + errors["wrong_orderbook"]?.add("Refresh rate is not an integer number") + } + else -> refreshInterval = it + } + } + + i++ + } + } + "--help", "-h" -> { println(usage) exitProcess(-1) @@ -250,7 +274,7 @@ fun main(args: Array) { when(command) { "orderbook" -> { try { - runBlocking { OrdersQueue.drawOrderBook(base, counter, 10) } + runBlocking { OrdersQueue.drawOrderBook(base, counter, refreshInterval) } } catch(e: Exception) { logger2(e.message + "\n" + e.stackTrace.joinToString("\n").red()) @@ -301,9 +325,9 @@ fun main(args: Array) { val launchTime = Date(System.currentTimeMillis() - startTime) println("huckster".italic() + " launched in " + - SimpleDateFormat("HH:mm:ss.SSS").apply{ + SimpleDateFormat("HH:mm:ss").apply{ timeZone = TimeZone.getTimeZone("UTC") - }.format(launchTime) + }.format(launchTime) + "\n" ) runBlocking { strategyAdapter.run() } diff --git a/src/main/kotlin/com/broxus/huckster/models/Account.kt b/src/main/kotlin/com/broxus/huckster/models/Account.kt index e8bd060..c063980 100644 --- a/src/main/kotlin/com/broxus/huckster/models/Account.kt +++ b/src/main/kotlin/com/broxus/huckster/models/Account.kt @@ -12,6 +12,6 @@ import com.google.gson.annotations.Expose */ data class Account( @Expose val userAddress: String, - @Expose val addressType: AddressType, + @Expose val addressType: String, @Expose val workspaceId: String ) \ No newline at end of file diff --git a/src/main/kotlin/com/broxus/huckster/models/PlaceOrderEvent.kt b/src/main/kotlin/com/broxus/huckster/models/PlaceOrderEvent.kt index 54fbde6..2e21ed0 100644 --- a/src/main/kotlin/com/broxus/huckster/models/PlaceOrderEvent.kt +++ b/src/main/kotlin/com/broxus/huckster/models/PlaceOrderEvent.kt @@ -5,7 +5,7 @@ import com.broxus.nova.types.AddressType data class PlaceOrderEvent( val userAddress: String, - val addressType: AddressType, + val addressType: String, val workspaceId: String?, var fromAmount: String, var toAmount: String, diff --git a/src/main/kotlin/com/broxus/nova/client/NovaApiService.kt b/src/main/kotlin/com/broxus/nova/client/NovaApiService.kt index 4b16bbf..c17232d 100644 --- a/src/main/kotlin/com/broxus/nova/client/NovaApiService.kt +++ b/src/main/kotlin/com/broxus/nova/client/NovaApiService.kt @@ -277,7 +277,7 @@ object NovaApiService { @Suppress("UNCHECKED_CAST") fun getSpecificUserBalance( userAddress: String, - addressType: AddressType, + addressType: String, workspaceId: String? = null ): List? { @@ -336,7 +336,7 @@ object NovaApiService { fun getSpecificUserOrders( id: String? = null, userAddress: String, - addressType: AddressType, + addressType: String, workspaceId: String? = null, base: String? = null, counter: String? = null, @@ -398,7 +398,7 @@ object NovaApiService { fun createLimitOrder( id: String, userAddress: String, - addressType: AddressType, + addressType: String, workspaceId: String? = null, from: String, to: String, diff --git a/src/main/kotlin/com/broxus/nova/models/ExchangeLimitInput.kt b/src/main/kotlin/com/broxus/nova/models/ExchangeLimitInput.kt index c1c6d2b..51aab17 100644 --- a/src/main/kotlin/com/broxus/nova/models/ExchangeLimitInput.kt +++ b/src/main/kotlin/com/broxus/nova/models/ExchangeLimitInput.kt @@ -19,7 +19,7 @@ import com.google.gson.annotations.Expose data class ExchangeLimitInput ( @Expose val id: String, @Expose val userAddress: String, - @Expose val addressType: AddressType, + @Expose val addressType: String, @Expose val workspaceId: String?, @Expose val from: String, @Expose val to: String, diff --git a/src/main/kotlin/com/broxus/nova/models/ExchangeSearchInput.kt b/src/main/kotlin/com/broxus/nova/models/ExchangeSearchInput.kt index 4422253..ee3f2e7 100644 --- a/src/main/kotlin/com/broxus/nova/models/ExchangeSearchInput.kt +++ b/src/main/kotlin/com/broxus/nova/models/ExchangeSearchInput.kt @@ -25,7 +25,7 @@ import com.google.gson.annotations.Expose data class ExchangeSearchInput ( @Expose val id: String?, @Expose val userAddress: String, - @Expose val addressType: AddressType, + @Expose val addressType: String, @Expose val workspaceId: String?, @Expose val base: String?, @Expose val counter: String?, diff --git a/src/main/kotlin/com/broxus/nova/models/UserAccountInput.kt b/src/main/kotlin/com/broxus/nova/models/UserAccountInput.kt index 8b5e2b9..9f2915f 100644 --- a/src/main/kotlin/com/broxus/nova/models/UserAccountInput.kt +++ b/src/main/kotlin/com/broxus/nova/models/UserAccountInput.kt @@ -5,6 +5,6 @@ import com.google.gson.annotations.Expose data class UserAccountInput ( @Expose val userAddress: String, - @Expose val addressType: AddressType, + @Expose val addressType: String, @Expose val workspaceId: String? ) \ No newline at end of file