diff --git a/android/TradeBotMonitor/app/build.gradle.kts b/android/TradeBotMonitor/app/build.gradle.kts index a9746ed..0c3964c 100644 --- a/android/TradeBotMonitor/app/build.gradle.kts +++ b/android/TradeBotMonitor/app/build.gradle.kts @@ -10,7 +10,7 @@ android { applicationId = "xyz.kusoft.tradebotmonitor" minSdk = 26 targetSdk = 36 - versionCode = 8 - versionName = "0.2.5" + versionCode = 9 + versionName = "0.2.6" } } diff --git a/android/TradeBotMonitor/app/src/main/java/xyz/kusoft/tradebotmonitor/MainActivity.kt b/android/TradeBotMonitor/app/src/main/java/xyz/kusoft/tradebotmonitor/MainActivity.kt index 3d431a0..9980f7c 100644 --- a/android/TradeBotMonitor/app/src/main/java/xyz/kusoft/tradebotmonitor/MainActivity.kt +++ b/android/TradeBotMonitor/app/src/main/java/xyz/kusoft/tradebotmonitor/MainActivity.kt @@ -640,7 +640,7 @@ class MainActivity : Activity() { addView(thinDivider().top(dp(12))) addView(trainingProcessPanel(coordination)) if (retrain.optBoolean("available", false)) { - addView(keyValueLine("Последний guard", if (retrain.optBoolean("accepted", false)) "модель принята" else "модель отклонена", if (retrain.optBoolean("accepted", false)) palette.green else palette.amber).top(dp(10))) + addView(guardSummaryPanel(retrain).top(dp(10))) } val replay = backtest.optJSONObject("full_replay") ?: JSONObject() addView(keyValueLine("Replay сделок", replay.optInt("trades", 0).toString()).top(dp(4))) @@ -678,6 +678,36 @@ class MainActivity : Activity() { } } + private fun guardSummaryPanel(retrain: JSONObject): View = + LinearLayout(this).apply { + val accepted = retrain.optBoolean("accepted", false) + val reason = retrain.optStringClean("reason") + val candidate = retrain.optJSONObject("candidate") ?: JSONObject() + val current = retrain.optJSONObject("current") ?: JSONObject() + val candidateScore = candidate.optDouble("score", Double.NaN) + val currentScore = current.optDouble("score", Double.NaN) + val status = if (accepted) { + "новая модель принята" + } else { + "candidate обучен, но gate не дал ему ходу" + } + val explanation = if (accepted) { + "Новая модель прошла проверку и стала действующей." + } else { + when (reason) { + "candidate_score_regressed_vs_current" -> "Обучение завершилось, но текущая модель сильнее по проверке. Бот оставил действующую модель." + else -> "Обучение завершилось, но защитный gate не разрешил заменить действующую модель." + } + } + + 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))) + } + } + private fun liveSettingsBlock(data: BotSnapshot?): View { val config = data?.config ?: JSONObject() return LinearLayout(this).apply {