|
|
|
@@ -328,7 +328,7 @@ class MainActivity : Activity() {
|
|
|
|
|
val reason = signal?.reason?.ifBlank { market.forecast?.reason.orEmpty() } ?: market.forecast?.reason.orEmpty()
|
|
|
|
|
|
|
|
|
|
setText(binding.price, price(latestPrice(market)), colorForSigned(edge))
|
|
|
|
|
setText(binding.edgeLine, "Edge ${signedPercent(edge, 2)} · P(up) ${probability(probability)} · 1h")
|
|
|
|
|
setText(binding.edgeLine, "Edge ${signedPercent(edge, 2)} · ${probabilityMetricLabel(signal, market.forecast)} ${probability(probability)} · 1h")
|
|
|
|
|
setText(binding.equity, money(data.account.equity))
|
|
|
|
|
setText(
|
|
|
|
|
binding.status,
|
|
|
|
@@ -553,7 +553,7 @@ class MainActivity : Activity() {
|
|
|
|
|
addView(priceText.top(dp(6)))
|
|
|
|
|
val edge = signal?.expectedReturnPercent ?: market.forecast?.expectedReturnPercent ?: 0.0
|
|
|
|
|
val probability = signal?.probabilityUp ?: market.forecast?.probabilityUp ?: 0.0
|
|
|
|
|
val edgeLine = text("Edge ${signedPercent(edge, 2)} · P(up) ${probability(probability)} · 1h", 13f, Typeface.NORMAL, palette.muted)
|
|
|
|
|
val edgeLine = text("Edge ${signedPercent(edge, 2)} · ${probabilityMetricLabel(signal, market.forecast)} ${probability(probability)} · 1h", 13f, Typeface.NORMAL, palette.muted)
|
|
|
|
|
tradingBindings?.edgeLine = edgeLine
|
|
|
|
|
addView(edgeLine.top(dp(2)))
|
|
|
|
|
}, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
|
|
|
|
@@ -728,7 +728,7 @@ class MainActivity : Activity() {
|
|
|
|
|
addView(LinearLayout(this@MainActivity).apply {
|
|
|
|
|
orientation = LinearLayout.VERTICAL
|
|
|
|
|
addView(text(market.symbol, 15f, Typeface.BOLD))
|
|
|
|
|
addView(text("P(up) ${probability(probability)} · Kelly ${number(signal?.positionNotionalUsdt ?: 0.0, 1)}", 11f, Typeface.NORMAL, palette.muted).top(dp(3)))
|
|
|
|
|
addView(text("${probabilityMetricLabel(signal, market.forecast)} ${probability(probability)} · Kelly ${number(signal?.positionNotionalUsdt ?: 0.0, 1)}", 11f, Typeface.NORMAL, palette.muted).top(dp(3)))
|
|
|
|
|
}, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1.7f))
|
|
|
|
|
|
|
|
|
|
addView(text(price(latestPrice(market)), 14f, Typeface.BOLD).apply {
|
|
|
|
@@ -778,7 +778,7 @@ class MainActivity : Activity() {
|
|
|
|
|
addView(LinearLayout(this@MainActivity).apply {
|
|
|
|
|
orientation = LinearLayout.HORIZONTAL
|
|
|
|
|
addView(text("Edge: ${signedPercent(signal?.expectedReturnPercent ?: forecast?.expectedReturnPercent ?: 0.0, 2)}", 13f, Typeface.NORMAL, palette.green), LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
|
|
|
|
|
addView(text("P(up): ${probability(signal?.probabilityUp ?: forecast?.probabilityUp ?: 0.0)}", 13f, Typeface.NORMAL, palette.green), LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
|
|
|
|
|
addView(text("${probabilityMetricLabel(signal, forecast)}: ${probability(signal?.probabilityUp ?: forecast?.probabilityUp ?: 0.0)}", 13f, Typeface.NORMAL, palette.green), LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
|
|
|
|
|
})
|
|
|
|
|
addView(text(signal?.reason?.ifBlank { forecast?.reason.orEmpty() }?.ifBlank { "Нет объяснения от модели" } ?: "Нет объяснения от модели", 13f, Typeface.BOLD).top(dp(12)))
|
|
|
|
|
val candle = market.candles.lastOrNull()
|
|
|
|
@@ -1090,17 +1090,32 @@ class MainActivity : Activity() {
|
|
|
|
|
val coordination = retrain.optJSONObject("coordination") ?: JSONObject()
|
|
|
|
|
val activeJob = coordination.optJSONObject("active_job")
|
|
|
|
|
val isTrainingActive = retrainRequestInFlight || (activeJob?.optStringClean("status") in setOf("pending", "running"))
|
|
|
|
|
val latestEvaluation = latestTrainingEvaluation(retrain)
|
|
|
|
|
val displayedEvaluation = latestEvaluation ?: retrain
|
|
|
|
|
val replay = displayedEvaluation
|
|
|
|
|
.optJSONObject("candidate")
|
|
|
|
|
?.optJSONObject("full_replay")
|
|
|
|
|
?: backtest.optJSONObject("full_replay")
|
|
|
|
|
?: JSONObject()
|
|
|
|
|
return LinearLayout(this).apply {
|
|
|
|
|
orientation = LinearLayout.VERTICAL
|
|
|
|
|
addView(trainingComputerPanel(retrain).top(dp(12)))
|
|
|
|
|
addView(thinDivider().top(dp(12)))
|
|
|
|
|
addView(trainingProcessPanel(coordination))
|
|
|
|
|
if (retrain.optBoolean("available", false)) {
|
|
|
|
|
addView(guardSummaryPanel(retrain).top(dp(10)))
|
|
|
|
|
if (displayedEvaluation.optJSONObject("candidate") != null) {
|
|
|
|
|
addView(guardSummaryPanel(displayedEvaluation).top(dp(10)))
|
|
|
|
|
}
|
|
|
|
|
val replay = backtest.optJSONObject("full_replay") ?: JSONObject()
|
|
|
|
|
addView(keyValueLine("Replay сделок", replay.optInt("trades", 0).toString()).top(dp(4)))
|
|
|
|
|
addView(keyValueLine("Replay PnL", signedMoney(replay.optDouble("net_pnl", 0.0)), colorForSigned(replay.optDouble("net_pnl", 0.0))).top(dp(4)))
|
|
|
|
|
val replayTotalPercent = replay.optDoubleOrNull("total_net_percent")
|
|
|
|
|
val replayAveragePercent = replay.optDoubleOrNull("avg_net_percent")
|
|
|
|
|
val replayPnl = when {
|
|
|
|
|
replayTotalPercent != null && replayAveragePercent != null ->
|
|
|
|
|
"${signedPercent(replayTotalPercent)} (средняя ${signedPercent(replayAveragePercent)})"
|
|
|
|
|
replayTotalPercent != null -> signedPercent(replayTotalPercent)
|
|
|
|
|
else -> signedMoney(replay.optDouble("net_pnl", 0.0))
|
|
|
|
|
}
|
|
|
|
|
val replayPnlValue = replayTotalPercent ?: replay.optDouble("net_pnl", 0.0)
|
|
|
|
|
addView(keyValueLine("Replay P&L", replayPnl, colorForSigned(replayPnlValue)).top(dp(4)))
|
|
|
|
|
if (isTrainingActive) {
|
|
|
|
|
addView(disabledActionButton(if (retrainRequestInFlight) "Отправляю команду" else "Обучение уже идет").top(dp(10)))
|
|
|
|
|
} else {
|
|
|
|
@@ -1163,11 +1178,26 @@ class MainActivity : Activity() {
|
|
|
|
|
orientation = LinearLayout.VERTICAL
|
|
|
|
|
addView(keyValueLine("Последний gate", status, if (accepted) palette.green else palette.amber))
|
|
|
|
|
addView(text(explanation, 12f, Typeface.NORMAL, palette.text).top(dp(7)))
|
|
|
|
|
if (!candidateScore.isNaN() && !currentScore.isNaN()) {
|
|
|
|
|
addView(keyValueLine("Score candidate / текущая", "${number(candidateScore, 2)} / ${number(currentScore, 2)}", if (accepted) palette.green else palette.amber).top(dp(7)))
|
|
|
|
|
when {
|
|
|
|
|
!candidateScore.isNaN() && !currentScore.isNaN() ->
|
|
|
|
|
addView(keyValueLine("SCORE кандидата / действующей", "${number(candidateScore, 3)} / ${number(currentScore, 3)}", if (accepted) palette.green else palette.amber).top(dp(7)))
|
|
|
|
|
!candidateScore.isNaN() ->
|
|
|
|
|
addView(keyValueLine("SCORE кандидата", number(candidateScore, 3), if (accepted) palette.green else palette.amber).top(dp(7)))
|
|
|
|
|
!currentScore.isNaN() ->
|
|
|
|
|
addView(keyValueLine("SCORE действующей", number(currentScore, 3), palette.muted).top(dp(7)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun latestTrainingEvaluation(retrain: JSONObject): JSONObject? {
|
|
|
|
|
val latestJob = retrain
|
|
|
|
|
.optJSONObject("coordination")
|
|
|
|
|
?.optJSONObject("latest_job")
|
|
|
|
|
?: return null
|
|
|
|
|
if (latestJob.optStringClean("status") !in setOf("completed", "failed")) return null
|
|
|
|
|
val summary = latestJob.optJSONObject("summary") ?: return null
|
|
|
|
|
return summary.takeIf { it.optJSONObject("candidate") != null }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun liveSettingsBlock(data: BotSnapshot?): View {
|
|
|
|
|
val config = data?.config ?: JSONObject()
|
|
|
|
|
return LinearLayout(this).apply {
|
|
|
|
@@ -1812,6 +1842,12 @@ class MainActivity : Activity() {
|
|
|
|
|
private fun probability(value: Double): String =
|
|
|
|
|
percent(normalizedProbability(value), 0)
|
|
|
|
|
|
|
|
|
|
private fun probabilityMetricLabel(signal: SignalData?, forecast: ForecastData?): String =
|
|
|
|
|
if (
|
|
|
|
|
signal?.targetTransform == "barrier_net_return" ||
|
|
|
|
|
forecast?.targetTransform == "barrier_net_return"
|
|
|
|
|
) "P(TP<SL)" else "P(up)"
|
|
|
|
|
|
|
|
|
|
private fun normalizedProbability(value: Double): Double =
|
|
|
|
|
if (abs(value) <= 1.0) value * 100.0 else value
|
|
|
|
|
|
|
|
|
|