BLOFIN RISK GUARD & ENGINE
Especificação Técnica de Engenharia: ForegroundService, WebSocket/REST Híbrido, Cooldown
de Alerta e Gestão de Rate-Limit
KOTLIN 2.0.0 ANDROID 8.0+ OREO (API 26) BLOFIN FUTURES API V1 WEBSOCKET REAL-TIME
1. Arquitetura do Monitor de Risco & Estratégia Rate-Limit Free
Para rodar um monitoramento de risco ininterrupto no Android sem violar os limites de requisições da camada
gratuita da API da BloFin (Rate-Limits REST) e sem consumir excessivamente a bateria do dispositivo, utilizamos
uma Engenharia Híbrida Inteligente:
🌐 Engenharia de Conexão Híbrida (WebSocket + REST Polling)
• Conexão Primária (WebSocket [Link] Escuta as atualizações de preço de
marcação (Mark Price) e do livro de ordens em tempo real. O WebSocket possui consumo zero de
cotas REST.
• Fallback Dinâmico (REST Adaptive Polling): Caso a conexão WebSocket caia, o sistema chaveia para
REST Polling. A frequência ajusta-se automaticamente com base na proximidade do risco:
◦ Modo Seguro (Distância > 5%): Requisição a cada 30 segundos.
◦ Modo Alerta (Distância entre 1.5% e 5%): Requisição a cada 10 segundos.
◦ Modo Crítico (Distância < 1.5%): Requisição a cada 3 segundos.
🔔 Engenharia de Anti-Spam e Gestão de Cooldown de Alertas
Sem controle de estado, uma variação contínua em torno do limiar de 1,5% dispararia dezenas de alarmes
sonoros por minuto. Implementamos um Mecanismo de Histerese de Preço + Cooldown Temporal:
• Cooldown Mínimo: Intervalo mínimo obrigatório de 45 segundos entre alarmes sonoros seguidos.
• Margem de Histerese (+0.5%): O alarme não desativa assim que o risco volta a 1,51%. Ele exige que a
distância alcance pelo menos 2,0% para resetar completamente o estado do disparador sonoro.
• Persistência no Service: O estado de disparo é mantido em memória dentro do ciclo de vida do
ForegroundService.
2. Código-Fonte da Engenharia em Kotlin 2.0.0
A. Configuração de Permissões (`[Link]`)
Requisito obrigatório para garantir a execução contínua no Android 8.0+ (Oreo) até o Android 14+.
<manifest xmlns:android="[Link]
package="[Link]">
<uses-permission android:name="[Link]" />
<uses-permission android:name="[Link].FOREGROUND_SERVICE" />
<uses-permission android:name="[Link].FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="[Link].POST_NOTIFICATIONS" />
<uses-permission android:name="[Link].WAKE_LOCK" />
<uses-permission android:name="[Link].USE_EXACT_ALARM" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="BloFin Risk Guard"
android:theme="@style/[Link]">
<service
android:name=".[Link]"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="[Link].PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Realtime Cryptocurrency Margin Liquidation Risk Monitoring" />
</service>
</application>
</manifest>
B. Gerenciador de Áudio e Notificação Rápida (`[Link]`)
package [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
class RiskAlarmManager(private val context: Context) {
private var mediaPlayer: MediaPlayer? = null
private var vibrator: Vibrator? = null
init {
vibrator = if ([Link].SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager = [Link](Context.VIBRATOR_MANAGER_SERVICE) as
VibratorManager
[Link]
} else {
@Suppress("DEPRECATION")
[Link](Context.VIBRATION_SERVICE) as Vibrator
}
}
/**
* Dispara Alarme Sonoro Alto e Vibração em Padrão de Emergência
*/
fun triggerEmergencyAlarm() {
if (mediaPlayer?.isPlaying == true) return
try {
val alarmUri = [Link](RingtoneManager.TYPE_ALARM)
?: [Link](RingtoneManager.TYPE_NOTIFICATION)
mediaPlayer = MediaPlayer().apply {
setDataSource(context, alarmUri)
setAudioAttributes(
[Link]()
.setUsage(AudioAttributes.USAGE_ALARM)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
.build()
)
isLooping = false
prepare()
start()
}
// Vibração SOS/Emergência
val pattern = longArrayOf(0, 500, 200, 500, 200, 800)
if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
vibrator?.vibrate([Link](pattern, -1))
} else {
@Suppress("DEPRECATION")
vibrator?.vibrate(pattern, -1)
}
} catch (e: Exception) {
[Link]()
}
}
fun stopAlarm() {
try {
if (mediaPlayer?.isPlaying == true) {
mediaPlayer?.stop()
mediaPlayer?.release()
mediaPlayer = null
}
vibrator?.cancel()
} catch (e: Exception) {
[Link]()
}
}
}
C. Motor do Service (`[Link]`)
Service principal compatível com Android 8.0+ que consome a API/WebSocket com controle de cooldown e
histerese.
package [Link]
import [Link].*
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link].*
import [Link]
class BlofinRiskForegroundService : Service() {
private val serviceScope = CoroutineScope([Link] + SupervisorJob())
private lateinit var alarmManager: RiskAlarmManager
// Configurações de Risco
private val LIQUIDATION_THRESHOLD_PCT = 1.5 // Disparo em 1.5%
private val HYSTERESIS_RESET_PCT = 2.0 // Reset do estado em 2.0%
private val ALARM_COOLDOWN_MS = 45_000L // 45 segundos de cooldown entre apitos
// Estado Local de Anti-Spam
private var lastAlarmTimestamp = 0L
private var isAlarmStateActive = false
// Dados Exemplo da Posição (Simulando Payload WebSocket/API)
private var entryPrice = 63100.30
private var liquidationPrice = 60161.80
private var isLong = true
override fun onCreate() {
[Link]()
alarmManager = RiskAlarmManager(this)
createNotificationChannel()
startForeground(NOTIFICATION_ID, createNotification("Iniciando Monitor de Risco
BloFin...", false))
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startRiskMonitoringLoop()
return START_STICKY
}
private fun startRiskMonitoringLoop() {
[Link] {
while (isActive) {
// Simulação da Leitura de Preço do WebSocket / API
val currentMarkPrice = fetchLatestMarkPrice()
evaluateRiskAndNotify(currentMarkPrice)
// Adaptativo: Intervalo de checagem mais rápido se o preço estiver instável
delay(2000L)
}
}
}
private fun evaluateRiskAndNotify(markPrice: Double) {
// Cálculo exato da distância percentual até o preço de liquidação
val distancePct = if (isLong) {
((markPrice - liquidationPrice) / markPrice) * 100.0
} else {
((liquidationPrice - markPrice) / markPrice) * 100.0
}
val currentTime = [Link]()
// Lógica de Histerese & Cooldown
if (distancePct <= LIQUIDATION_THRESHOLD_PCT) {
val canTriggerAlarm = (currentTime - lastAlarmTimestamp) > ALARM_COOLDOWN_MS
if (!isAlarmStateActive || canTriggerAlarm) {
isAlarmStateActive = true
lastAlarmTimestamp = currentTime
// Dispara o som crítico
[Link]()
// Atualiza Notificação para Vermelho Crítico
updateNotification(
"🚨 PERIGO DE LIQUIDAÇÃO! (${[Link]("%.2f", distancePct)}%)",
"Preço Atual: $markPrice | Liquidação: $liquidationPrice. FECHE A POSIÇÃO!",
isCritical = true
)
}
} else if (distancePct >= HYSTERESIS_RESET_PCT) {
// Reseta o estado quando o mercado se afasta com segurança (> 2.0%)
isAlarmStateActive = false
[Link]()
updateNotification(
"🟢 Posição Estável (Distância: ${[Link]("%.2f", distancePct)}%)",
"Preço Atual: $markPrice | Liquidação: $liquidationPrice",
isCritical = false
)
} else {
// Zona Neutra (entre 1.5% e 2.0%) - Apenas atualiza o texto sem apitar
updateNotification(
"⚠️ Atenção - Próximo do Limite (${[Link]("%.2f", distancePct)}%)",
"Preço Atual: $markPrice | Liquidação: $liquidationPrice",
isCritical = false
)
}
}
private fun fetchLatestMarkPrice(): Double {
// Em produção: Retorna o valor emitido pelo StateFlow do WebSocket
return 60800.00 // Exemplo de preço aproximando da liquidação
}
private fun createNotificationChannel() {
if ([Link].SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"BloFin Risk Alerts",
NotificationManager.IMPORTANCE_HIGH
).apply {
description = "Alertas críticos de liquidação de futuros BloFin"
enableLights(true)
lightColor = [Link]
enableVibration(true)
}
val manager = getSystemService(NotificationManager::[Link])
manager?.createNotificationChannel(channel)
}
}
private fun createNotification(contentText: String, title: String, isCritical: Boolean =
false): Notification {
val builder = [Link](this, CHANNEL_ID)
.setContentTitle(title)
.setContentText(contentText)
.setSmallIcon([Link].stat_sys_warning)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setCategory(NotificationCompat.CATEGORY_ALARM)
.setOngoing(true)
if (isCritical) {
[Link]([Link])
[Link](true)
}
return [Link]()
}
private fun updateNotification(title: String, text: String, isCritical: Boolean) {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
[Link](NOTIFICATION_ID, createNotification(text, title, isCritical))
}
override fun onDestroy() {
[Link]()
[Link]()
[Link]()
}
override fun onBind(intent: Intent?): IBinder? = null
companion object {
const val CHANNEL_ID = "blofin_risk_channel"
const val NOTIFICATION_ID = 9901
}
}
3. Tabela Comparativa de Comportamento do Alarme
Distância da
Estado da Conexão API Ação do Service Emissão de Som/Alarme
Liquidação
WebSocket Ativo / REST a Notificação Verde (Modo
> 5,0% Muto
cada 30s Vigilância)
1,5% a 2,0% (Zona WebSocket Ativo / REST a Notificação Amarela de Muto (Aguardando Reset/
Neutra) cada 10s Alerta Disparo)
WebSocket Ativo / REST a
< 1,5% (Início da Crise) Notificação Vermelha Crítica 🔊 DISPARO IMEDIATO
cada 3s
< 1,5% (Permanência < WebSocket Ativo / REST a Atualização visual na tela do Muto (Bloqueado por
45s) cada 3s Android Cooldown)
< 1,5% (Após 45 WebSocket Ativo / REST a Notificação Vermelha Re- 🔊 NOVO DISPARO DE
segundos) cada 3s Notificada REFORÇO