Skip to content

Commit

Permalink
Improved webb app
Browse files Browse the repository at this point in the history
  • Loading branch information
jbaron committed Aug 16, 2023
1 parent 505fc79 commit f496256
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,34 @@ import org.roboquant.brokers.Account
import org.roboquant.feeds.Event
import org.roboquant.orders.Order
import org.roboquant.policies.Policy
import org.roboquant.strategies.Rating
import org.roboquant.strategies.Signal
import java.time.Instant

internal class PausablePolicy(private val policy: Policy, var pause: Boolean = false) : Policy by policy {

internal var totalSignals = 0
internal var sellSignals = 0
internal var holdSignals = 0
internal var buySignals = 0
internal var totalOrders = 0
internal var totalEvents = 0
internal var emptyEvents = 0
internal var totalActions = 0

internal var lastUpdate: Instant = Instant.MIN

override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
// Still invoke the policy so any state can be updated if required.
val orders = policy.act(signals, account, event)

totalSignals += signals.size

buySignals += signals.filter { it.rating.isPositive }.size
sellSignals += signals.filter { it.rating.isNegative }.size
holdSignals += signals.filter { it.rating == Rating.HOLD }.size

totalEvents++
if (event.actions.isEmpty()) emptyEvents++
totalActions += event.actions.size
lastUpdate = event.time

return if (! pause) {
Expand Down
24 changes: 21 additions & 3 deletions roboquant-server/src/main/kotlin/org/roboquant/server/hx.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")

package org.roboquant.server

import kotlinx.html.FORM
import kotlinx.html.HTMLTag

/*
fun HTMLTag.hxGet(value: String) {
attributes += "data-hx-get" to value
}
Expand All @@ -42,4 +41,23 @@ fun HTMLTag.hxBoost(value: Boolean) {
fun HTMLTag.hxExt(value: String) {
attributes += "data-hx-ext" to value
}
}
*/

var FORM.hxPost: String
get() = attributes["data-hx-post"] ?: ""
set(value) {
attributes["data-hx-post"] = value
}

var HTMLTag.hxExt: String
get() = attributes["data-hx-ext"] ?: ""
set(value) {
attributes["data-hx-ext"] = value
}

var HTMLTag.hxTarget: String
get() = attributes["data-hx-target"] ?: ""
set(value) {
attributes["data-hx-target"] = value
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ fun HTML.page(title: String, bodyFn: BODY.() -> Unit) {
href = "https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel = "stylesheet"
}

link {
href = "/static/custom.css"
rel = "stylesheet"
}
}
body(classes = "container") {
h1 { +title }
Expand Down
65 changes: 36 additions & 29 deletions roboquant-server/src/main/kotlin/org/roboquant/server/routes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ fun Route.getChart() {
}
}

@Suppress("LongMethod")
fun Route.listRuns() {
get("/") {
val params = call.request.queryParameters


if (params.contains("action")) {
val run = params["run"]!!
val action = params["action"]!!
Expand All @@ -91,6 +91,7 @@ fun Route.listRuns() {
tr {
th { +"run name" }
th { +"state" }
th { +"timeframe"}
th { +"events" }
th { +"signals" }
th { +"orders" }
Expand All @@ -99,26 +100,32 @@ fun Route.listRuns() {
}
runs.forEach { (run, info) ->
val policy = info.roboquant.policy as PausablePolicy
val state = if (policy.pause) "pause" else "running"
tr {
td { +run }
td { +state }
td { +policy.totalEvents.toString() }
td { +policy.totalSignals.toString() }
td { +if (policy.pause) "pause" else "running" }
td { +info.timeframe.toPrettyString() }
td {
+"total = ${policy.totalEvents}"
br
+"empty = ${policy.emptyEvents}"
br
+"actions = ${policy.totalActions}"
}
td {
+"buy = ${policy.buySignals}"
br
+"sell = ${policy.sellSignals}"
br
+"hold = ${policy.holdSignals}"
}
td { +policy.totalOrders.toString() }
td { +policy.lastUpdate.toString() }
td {
a(href = "/run/$run") {
+"details"
}
br {}
a(href = "/?action=pause&run=$run") {
+"pause"
}
br {}
a(href = "/?action=resume&run=$run") {
+"resume"
}
a(href = "/run/$run") { +"details" }
br
a(href = "/?action=pause&run=$run") { +"pause" }
br
a(href = "/?action=resume&run=$run") { +"resume" }
}
}
}
Expand All @@ -131,18 +138,19 @@ fun Route.listRuns() {
}


private fun FlowContent.echarts(id: String, width: String = "100%", height: String = "800px") {
private fun FlowContent.echarts(elemId: String, width: String = "100%", height: String = "800px") {
div {
attributes["id"] = id
hxExt("echarts")
id = elemId
hxExt = "echarts"
style = "width:$width;height:$height;"
}
}

fun FlowContent.metricForm(target: String, run: String, metricNames: List<String>) {
private fun FlowContent.metricForm(target: String, run: String, info: RunInfo) {
val metricNames = info.roboquant.logger.metricNames
form {
hxPost("/echarts")
hxTarget(target)
hxPost = "/echarts"
hxTarget = target
select(classes = "form-select") {
name = "metric"
metricNames.forEach {
Expand All @@ -153,14 +161,14 @@ fun FlowContent.metricForm(target: String, run: String, metricNames: List<String
input(type = InputType.hidden, name = "run") { value=run }

button(type = ButtonType.submit, classes = "btn btn-primary") {
+"Get Chart"
+"Update Chart"

}
}
}


fun List<List<Any>>.takeLastPlusHeader(n: Int): List<List<Any>> {
private fun List<List<Any>>.takeLastPlusHeader(n: Int): List<List<Any>> {
return listOf(first()) + drop(1).takeLast(n)
}

Expand All @@ -181,17 +189,16 @@ private fun getAccountSummary(acc: Account): List<List<Any>> {

fun Route.getRun() {
get("/run/{id}") {
val id = call.parameters["id"] ?: ""
val id = call.parameters.getOrFail("id")
val info = runs.getValue(id)
val metricNames = info.roboquant.logger.metricNames
val acc = info.roboquant.broker.account
call.respondHtml(HttpStatusCode.OK) {
page("Details $id") {
a(href = "/") { +"Back to overview" }
table("account summary", getAccountSummary(acc))
div(classes = "row my-4") {
div(classes = "col-2") {
metricForm("#echarts123456", id, metricNames)
metricForm("#echarts123456", id, info)
}
div(classes = "col-10") {
echarts("echarts123456", height = "400px")
Expand All @@ -200,8 +207,8 @@ fun Route.getRun() {
// table("cash", metric.getCash())
table("open positions", acc.positions.lines())
table("open orders", acc.openOrders.lines())
table("closed orders", acc.closedOrders.lines().takeLastPlusHeader(10))
table("trades", acc.trades.lines().takeLastPlusHeader(10))
table("closed orders (max 10)", acc.closedOrders.lines().takeLastPlusHeader(10))
table("trades (max 10)", acc.trades.lines().takeLastPlusHeader(10))
}
}
}
Expand Down
20 changes: 20 additions & 0 deletions roboquant-server/src/main/resources/custom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2020-2023 Neural Layer
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

caption {
caption-side: top;
font-size: 150%;
}
24 changes: 19 additions & 5 deletions roboquant-server/src/test/kotlin/org/roboquant/samples/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,40 @@ package org.roboquant.samples
import org.roboquant.Roboquant
import org.roboquant.common.ParallelJobs
import org.roboquant.common.Timeframe
import org.roboquant.feeds.RandomWalkFeed
import org.roboquant.common.hours
import org.roboquant.feeds.Feed
import org.roboquant.feeds.util.LiveTestFeed
import org.roboquant.metrics.AccountMetric
import org.roboquant.server.WebServer
import org.roboquant.strategies.EMAStrategy
import kotlin.random.Random
import kotlin.system.exitProcess


fun getFeed(): Feed {
var prev = 100.0
val randomPrices = (1..10_000).map {
val next = prev + Random.Default.nextDouble() - 0.5
prev = next
next
}
return LiveTestFeed(randomPrices, delayInMillis = 200)
}

fun main() {
val server = WebServer("test", "secret", 8080)
val feed = RandomWalkFeed.lastYears(20)
feed.delay = 100L

val jobs = ParallelJobs()
repeat(3) {
val tf = Timeframe.next(1.hours)
jobs.add {
val rq = Roboquant(EMAStrategy(), AccountMetric())
server.runAsync(rq,feed, Timeframe.INFINITE)
server.runAsync(rq, getFeed(), tf)
}
}
jobs.joinAllBlocking()
server.stop()
feed.close()
exitProcess(0)
}


Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class RandomWalkFeed(
private val priceRange: Double = 1.0,
template: Asset = Asset("ASSET"),
private val seed: Int = 42,
var delay: Long = 0L
private var delay: Long = 0L
) : HistoricFeed {

/**
Expand Down

0 comments on commit f496256

Please sign in to comment.