Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 795 | blopes | 1 | package br.com.kronus.binance.robos; |
| 2 | |||
| 3 | import java.io.BufferedReader; |
||
| 4 | import java.io.BufferedWriter; |
||
| 5 | import java.io.IOException; |
||
| 6 | import java.math.BigDecimal; |
||
| 7 | import java.nio.charset.StandardCharsets; |
||
| 8 | import java.nio.file.Files; |
||
| 9 | import java.nio.file.Path; |
||
| 10 | import java.nio.file.Paths; |
||
| 11 | import java.nio.file.StandardOpenOption; |
||
| 12 | import java.time.LocalDate; |
||
| 13 | import java.time.LocalDateTime; |
||
| 14 | import java.time.LocalTime; |
||
| 15 | import java.time.ZoneId; |
||
| 16 | import java.time.format.DateTimeFormatter; |
||
| 17 | import java.util.ArrayList; |
||
| 18 | import java.util.Comparator; |
||
| 19 | import java.util.Date; |
||
| 20 | import java.util.HashSet; |
||
| 21 | import java.util.List; |
||
| 22 | import java.util.Set; |
||
| 23 | import java.util.concurrent.Executors; |
||
| 24 | import java.util.concurrent.ScheduledExecutorService; |
||
| 25 | import java.util.concurrent.TimeUnit; |
||
| 26 | |||
| 27 | import br.com.kronus.core.Candle; |
||
| 28 | import br.com.kronus.core.DetectorGatilhos; |
||
| 29 | import br.com.kronus.core.EstrategiaGatilhoTipo3Sinais; |
||
| 30 | import br.com.kronus.core.PadraoGatilho; |
||
| 31 | import br.com.kronus.core.SinalTradeGatilho3; |
||
| 32 | import br.com.kronus.core.TipoPeriodoCandle; |
||
| 33 | |||
| 34 | /** |
||
| 35 | * RoboSinaisMain: |
||
| 36 | * |
||
| 37 | * - Monitora o arquivo BTCUSDT_1m.csv |
||
| 38 | * - A cada 2 segundos verifica se foi modificado |
||
| 39 | * - Se houve modificação: |
||
| 40 | * * Carrega TODOS os candles do arquivo |
||
| 41 | * * Identifica padrões e gera sinais (DetectorGatilhos + Estratégia) |
||
| 42 | * * Grava os sinais em SINAIS.csv com status 'P' (Pendente) |
||
| 43 | * * Evita gravar sinais duplicados via ID de sinal |
||
| 44 | * |
||
| 45 | * Layout BTCUSDT_1m.csv: |
||
| 46 | * BTCUSDT;30/11/2025;14:12:00;91390,2;91410,7;91390;91390;226,94;155 |
||
| 47 | * 0 1 2 3 4 5 6 7 8 |
||
| 48 | * |
||
| 49 | * Layout SINAIS.csv (mesmo do RoboGatilhosSinaisMain): |
||
| 50 | * ID;Ativo;Data;Hora;TipoOperacao;PrecoEntrada;StopLoss;TakeProfit;Quantidade;Status;ClientOrderId |
||
| 51 | */ |
||
| 52 | public class RoboSinaisMain { |
||
| 53 | |||
| 54 | private static final String ARQUIVO_SINAIS = "D:/Dropbox/BLP/INVESTIMENTOS/DAYTRADE/sinais/SINAIS.csv"; |
||
| 55 | private static final String ARQUIVO_CANDLES = "D:/Dropbox/BLP/INVESTIMENTOS/DAYTRADE/temporeal/BTCUSDT_5m.csv"; |
||
| 56 | |||
| 57 | private static final BigDecimal VALOR_ENTRADA = new BigDecimal(0.005); |
||
| 58 | |||
| 59 | private final ZoneId zone = ZoneId.of("America/Maceio"); |
||
| 60 | private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); |
||
| 61 | |||
| 62 | // Data/hora para candles (arquivo BTCUSDT_1m.csv) |
||
| 63 | private final DateTimeFormatter fmtDataCandle = DateTimeFormatter.ofPattern("dd/MM/yyyy"); |
||
| 64 | private final DateTimeFormatter fmtHoraCandle = DateTimeFormatter.ofPattern("HH:mm:ss"); |
||
| 65 | |||
| 66 | // Data/hora para sinais (arquivo SINAIS.csv) |
||
| 67 | private final DateTimeFormatter fmtDataSinal = DateTimeFormatter.ofPattern("dd/MM/yyyy"); |
||
| 68 | private final DateTimeFormatter fmtHoraSinal = DateTimeFormatter.ofPattern("HH:mm:ss"); |
||
| 69 | |||
| 70 | // Controle de modificação do arquivo de candles |
||
| 71 | private long ultimaModificacaoCandles = 0L; |
||
| 72 | |||
| 73 | // IDs de sinais já gravados no arquivo SINAIS.csv (para evitar duplicidade) |
||
| 74 | private final Set<String> idsSinaisGravados = new HashSet<>(); |
||
| 75 | |||
| 76 | // ===================================================================== |
||
| 77 | // MAIN |
||
| 78 | // ===================================================================== |
||
| 79 | public static void main(String[] args) { |
||
| 80 | try { |
||
| 81 | RoboSinaisMain r = new RoboSinaisMain(); |
||
| 82 | r.iniciar(); |
||
| 83 | |||
| 84 | Runtime.getRuntime().addShutdownHook(new Thread(r::parar)); |
||
| 85 | } catch (Exception e) { |
||
| 86 | e.printStackTrace(); |
||
| 87 | } |
||
| 88 | } |
||
| 89 | |||
| 90 | // ===================================================================== |
||
| 91 | // CONSTRUTOR / INÍCIO / FIM |
||
| 92 | // ===================================================================== |
||
| 93 | public RoboSinaisMain() { |
||
| 94 | System.out.println("[ROBO-SINAIS] Inicializando RoboSinaisMain..."); |
||
| 95 | carregarIdsSinaisExistentes(); |
||
| 96 | } |
||
| 97 | |||
| 98 | public void iniciar() { |
||
| 99 | System.out.println("[ROBO-SINAIS] Iniciando monitor de candles (BTCUSDT_1m) a cada 2 segundos..."); |
||
| 100 | |||
| 101 | scheduler.scheduleAtFixedRate(() -> { |
||
| 102 | try { |
||
| 103 | ciclo(); |
||
| 104 | } catch (Exception e) { |
||
| 105 | e.printStackTrace(); |
||
| 106 | } |
||
| 107 | }, 0, 2, TimeUnit.SECONDS); |
||
| 108 | } |
||
| 109 | |||
| 110 | public void parar() { |
||
| 111 | System.out.println("[ROBO-SINAIS] Encerrando RoboSinaisMain."); |
||
| 112 | scheduler.shutdownNow(); |
||
| 113 | } |
||
| 114 | |||
| 115 | // ===================================================================== |
||
| 116 | // CICLO A CADA 2 SEGUNDOS |
||
| 117 | // ===================================================================== |
||
| 118 | private void ciclo() { |
||
| 119 | if (arquivoCandlesFoiModificado()) { |
||
| 120 | System.out.println("[ROBO-SINAIS] BTCUSDT_1m.csv modificado. Recarregando candles e gerando sinais..."); |
||
| 121 | |||
| 122 | List<Candle> candles = carregarCandlesDoArquivo(); |
||
| 123 | if (candles.isEmpty()) { |
||
| 124 | System.out.println("[ROBO-SINAIS] Nenhum candle carregado. Ignorando ciclo."); |
||
| 125 | return; |
||
| 126 | } |
||
| 127 | |||
| 128 | // 1) Detector de padrões |
||
| 129 | DetectorGatilhos detector = new DetectorGatilhos(); |
||
| 130 | List<PadraoGatilho> padroes = detector.identificarPadroes(candles); |
||
| 131 | |||
| 132 | if (padroes == null || padroes.isEmpty()) { |
||
| 133 | System.out.println("[ROBO-SINAIS] Nenhum padrão encontrado."); |
||
| 134 | return; |
||
| 135 | } |
||
| 136 | |||
| 137 | System.out.println("[ROBO-SINAIS] Padrões encontrados: " + padroes.size()); |
||
| 138 | |||
| 139 | // 2) Estratégia para gerar sinais |
||
| 140 | EstrategiaGatilhoTipo3Sinais estrategia = new EstrategiaGatilhoTipo3Sinais(); |
||
| 141 | List<SinalTradeGatilho3> sinais = estrategia.gerarSinais(padroes); |
||
| 142 | |||
| 143 | if (sinais == null || sinais.isEmpty()) { |
||
| 144 | System.out.println("[ROBO-SINAIS] Nenhum sinal gerado."); |
||
| 145 | return; |
||
| 146 | } |
||
| 147 | |||
| 148 | // Ordena por data/hora de entrada DESC (mais recente primeiro) |
||
| 149 | sinais.sort(Comparator.comparing(SinalTradeGatilho3::getDataHoraEntrada).reversed()); |
||
| 150 | |||
| 151 | System.out.println("[ROBO-SINAIS] Sinais gerados: " + sinais.size()); |
||
| 152 | |||
| 153 | // 3) Grava apenas os sinais ainda não registrados em SINAIS.csv |
||
| 154 | gravarNovosSinais(sinais); |
||
| 155 | } |
||
| 156 | } |
||
| 157 | |||
| 158 | private boolean arquivoCandlesFoiModificado() { |
||
| 159 | try { |
||
| 160 | Path path = Paths.get(ARQUIVO_CANDLES); |
||
| 161 | if (!Files.exists(path)) { |
||
| 162 | System.out.println("[ROBO-SINAIS] Arquivo de candles não encontrado: " + path.toAbsolutePath()); |
||
| 163 | return false; |
||
| 164 | } |
||
| 165 | long mod = Files.getLastModifiedTime(path).toMillis(); |
||
| 166 | if (mod > ultimaModificacaoCandles) { |
||
| 167 | ultimaModificacaoCandles = mod; |
||
| 168 | return true; |
||
| 169 | } |
||
| 170 | } catch (IOException e) { |
||
| 171 | System.err.println("[ROBO-SINAIS] Erro ao verificar modificação do arquivo de candles: " + e.getMessage()); |
||
| 172 | } |
||
| 173 | return false; |
||
| 174 | } |
||
| 175 | |||
| 176 | // ===================================================================== |
||
| 177 | // CARREGAR CANDLES DO ARQUIVO BTCUSDT_1m.csv |
||
| 178 | // ===================================================================== |
||
| 179 | private List<Candle> carregarCandlesDoArquivo() { |
||
| 180 | List<Candle> lista = new ArrayList<>(); |
||
| 181 | Path path = Paths.get(ARQUIVO_CANDLES); |
||
| 182 | |||
| 183 | if (!Files.exists(path)) { |
||
| 184 | System.out.println("[ROBO-SINAIS] Arquivo de candles não encontrado: " + path.toAbsolutePath()); |
||
| 185 | return lista; |
||
| 186 | } |
||
| 187 | |||
| 188 | try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { |
||
| 189 | String linha; |
||
| 190 | boolean primeira = true; |
||
| 191 | int contador = 0; |
||
| 192 | |||
| 193 | while ((linha = reader.readLine()) != null) { |
||
| 194 | if (primeira && linha.toLowerCase().contains("data")) { |
||
| 195 | primeira = false; |
||
| 196 | continue; // pula cabeçalho, se houver |
||
| 197 | } |
||
| 198 | primeira = false; |
||
| 199 | |||
| 200 | if (linha.trim().isEmpty()) continue; |
||
| 201 | |||
| 202 | Candle c = parseLinhaParaCandle(linha, contador); |
||
| 203 | if (c != null) { |
||
| 204 | lista.add(c); |
||
| 205 | contador++; |
||
| 206 | } |
||
| 207 | } |
||
| 208 | |||
| 209 | System.out.println("[ROBO-SINAIS] Candles carregados: " + lista.size()); |
||
| 210 | |||
| 211 | } catch (IOException e) { |
||
| 212 | System.err.println("[ROBO-SINAIS] Erro ao ler arquivo de candles: " + e.getMessage()); |
||
| 213 | e.printStackTrace(); |
||
| 214 | } |
||
| 215 | |||
| 216 | return lista; |
||
| 217 | } |
||
| 218 | |||
| 219 | /** |
||
| 220 | * Formato BTCUSDT_1m.csv: |
||
| 221 | * BTCUSDT;30/11/2025;14:12:00;91390,2;91410,7;91390;91390;226,94;155 |
||
| 222 | * 0 1 2 3 4 5 6 7 8 |
||
| 223 | */ |
||
| 224 | private Candle parseLinhaParaCandle(String linha, int contadorCandle) { |
||
| 225 | try { |
||
| 226 | String[] p = linha.split(";", -1); |
||
| 227 | if (p.length < 7) { |
||
| 228 | System.out.println("[ROBO-SINAIS] Linha inválida em BTCUSDT_1m: " + linha); |
||
| 229 | return null; |
||
| 230 | } |
||
| 231 | |||
| 232 | String ativo = p[0].trim(); |
||
| 233 | |||
| 234 | LocalDate data = LocalDate.parse(p[1].trim(), fmtDataCandle); |
||
| 235 | LocalTime hora = LocalTime.parse(p[2].trim(), fmtHoraCandle); |
||
| 236 | LocalDateTime dt = LocalDateTime.of(data, hora); |
||
| 237 | |||
| 238 | BigDecimal abertura = parseBigDecimal(p[3]); |
||
| 239 | BigDecimal maxima = parseBigDecimal(p[4]); |
||
| 240 | BigDecimal minima = parseBigDecimal(p[5]); |
||
| 241 | BigDecimal fechamento = parseBigDecimal(p[6]); |
||
| 242 | BigDecimal volume = (p.length > 7) ? parseBigDecimal(p[7]) : BigDecimal.ZERO; |
||
| 243 | |||
| 244 | Candle c = new Candle( |
||
| 245 | contadorCandle, |
||
| 246 | ativo, |
||
| 247 | dt, |
||
| 248 | abertura, |
||
| 249 | maxima, |
||
| 250 | minima, |
||
| 251 | fechamento, |
||
| 252 | TipoPeriodoCandle.M1.getValor() |
||
| 253 | ); |
||
| 254 | c.setVolume(volume); |
||
| 255 | |||
| 256 | return c; |
||
| 257 | |||
| 258 | } catch (Exception e) { |
||
| 259 | System.out.println("[ROBO-SINAIS] Erro ao parsear linha de candle: " + linha); |
||
| 260 | e.printStackTrace(); |
||
| 261 | return null; |
||
| 262 | } |
||
| 263 | } |
||
| 264 | |||
| 265 | // ===================================================================== |
||
| 266 | // GRAVAÇÃO DE SINAIS EM SINAIS.csv (STATUS = P) |
||
| 267 | // ===================================================================== |
||
| 268 | private void gravarNovosSinais(List<SinalTradeGatilho3> sinais) { |
||
| 269 | if (sinais == null || sinais.isEmpty()) { |
||
| 270 | return; |
||
| 271 | } |
||
| 272 | |||
| 273 | Path path = Paths.get(ARQUIVO_SINAIS); |
||
| 274 | boolean novoArquivo = !Files.exists(path); |
||
| 275 | |||
| 276 | try { |
||
| 277 | if (path.getParent() != null && !Files.exists(path.getParent())) { |
||
| 278 | Files.createDirectories(path.getParent()); |
||
| 279 | } |
||
| 280 | |||
| 281 | try (BufferedWriter writer = Files.newBufferedWriter( |
||
| 282 | path, |
||
| 283 | StandardCharsets.UTF_8, |
||
| 284 | StandardOpenOption.CREATE, |
||
| 285 | StandardOpenOption.APPEND) |
||
| 286 | ) { |
||
| 287 | // Cabeçalho se arquivo recém-criado |
||
| 288 | if (novoArquivo) { |
||
| 289 | writer.write("ID;Ativo;Data;Hora;TipoOperacao;PrecoEntrada;StopLoss;TakeProfit;Quantidade;Status;ClientOrderId"); |
||
| 290 | writer.newLine(); |
||
| 291 | } |
||
| 292 | |||
| 293 | int novos = 0; |
||
| 294 | |||
| 295 | for (SinalTradeGatilho3 s : sinais) { |
||
| 296 | if (s == null) continue; |
||
| 297 | if (s.getDataHoraEntrada() == null) continue; |
||
| 298 | |||
| 299 | String id = gerarIdSinal(s); |
||
| 300 | |||
| 301 | // Evita duplicidade |
||
| 302 | if (idsSinaisGravados.contains(id)) { |
||
| 303 | continue; |
||
| 304 | } |
||
| 305 | |||
| 306 | idsSinaisGravados.add(id); |
||
| 307 | |||
| 308 | // ==== Mapear campos ==== |
||
| 309 | String ativo = s.getIdAtivo(); // AJUSTE AQUI se o getter tiver outro nome |
||
| 310 | if (ativo == null || ativo.trim().isEmpty()) { |
||
| 311 | ativo = "BTCUSDT"; |
||
| 312 | } |
||
| 313 | |||
| 314 | Date dataHora = s.getDataHoraEntrada(); |
||
| 315 | if (dataHora == null) { |
||
| 316 | return; // ou continue; dependendo do contexto |
||
| 317 | } |
||
| 318 | |||
| 319 | LocalDateTime dt = LocalDateTime.ofInstant(dataHora.toInstant(), zone); // use o ZoneId que você já tem na classe |
||
| 320 | String data = dt.toLocalDate().format(fmtDataSinal); |
||
| 321 | String hora = dt.toLocalTime().format(fmtHoraSinal); |
||
| 322 | |||
| 323 | |||
| 324 | String tipoOperacao = (s.getTipoOperacao() != null)? s.getTipoOperacao().getValor() : ""; |
||
| 325 | |||
| 326 | BigDecimal precoEntrada = s.getPrecoEntrada1(); |
||
| 327 | BigDecimal stopLoss = s.getStopMenos100(); |
||
| 328 | BigDecimal takeProfit = s.getAlvo2(); |
||
| 329 | BigDecimal quantidade = VALOR_ENTRADA; // AJUSTE se usar outro nome (ex.: getContratos) |
||
| 330 | |||
| 331 | String precoEntradaStr = toStr(precoEntrada); |
||
| 332 | String stopLossStr = toStr(stopLoss); |
||
| 333 | String takeProfitStr = toStr(takeProfit); |
||
| 334 | String quantidadeStr = toStr(quantidade); |
||
| 335 | |||
| 336 | String status = "P"; // sempre pendente ao gravar |
||
| 337 | String clientOrderId = ""; // ainda não enviado pra Binance |
||
| 338 | |||
| 339 | String linha = id + ";" + |
||
| 340 | ativo + ";" + |
||
| 341 | data + ";" + |
||
| 342 | hora + ";" + |
||
| 343 | tipoOperacao + ";" + |
||
| 344 | precoEntradaStr + ";" + |
||
| 345 | stopLossStr + ";" + |
||
| 346 | takeProfitStr + ";" + |
||
| 347 | quantidadeStr + ";" + |
||
| 348 | status + ";" + |
||
| 349 | clientOrderId; |
||
| 350 | |||
| 351 | writer.write(linha); |
||
| 352 | writer.newLine(); |
||
| 353 | novos++; |
||
| 354 | } |
||
| 355 | |||
| 356 | System.out.println("[ROBO-SINAIS] Novos sinais gravados: " + novos); |
||
| 357 | |||
| 358 | } |
||
| 359 | |||
| 360 | } catch (IOException e) { |
||
| 361 | System.err.println("[ROBO-SINAIS] Erro ao gravar SINAIS.csv: " + e.getMessage()); |
||
| 362 | e.printStackTrace(); |
||
| 363 | } |
||
| 364 | } |
||
| 365 | |||
| 366 | /** |
||
| 367 | * Gera um ID único para o sinal, baseado em: |
||
| 368 | * Ativo + DataHoraEntrada + TipoOperacao + PrecoEntrada |
||
| 369 | * |
||
| 370 | * Isso garante que, se o mesmo sinal for recalculado depois, |
||
| 371 | * não será gravado novamente. |
||
| 372 | */ |
||
| 373 | private String gerarIdSinal(SinalTradeGatilho3 s) { |
||
| 374 | String ativo = s.getIdAtivo(); |
||
| 375 | if (ativo == null || ativo.trim().isEmpty()) { |
||
| 376 | ativo = "BTCUSDT"; |
||
| 377 | } |
||
| 378 | |||
| 379 | Date dataHora = s.getDataHoraEntrada(); |
||
| 380 | LocalDateTime dt = LocalDateTime.ofInstant( |
||
| 381 | dataHora.toInstant(), |
||
| 382 | zone |
||
| 383 | ); |
||
| 384 | |||
| 385 | String tipo = (s.getTipoOperacao() != null) ? s.getTipoOperacao().getValor() : ""; |
||
| 386 | String preco = (s.getAlvo1() != null)? s.getAlvo1().toPlainString(): "0"; |
||
| 387 | |||
| 388 | return ativo + "_" + dt.toString() + "_" + tipo + "_" + preco; |
||
| 389 | } |
||
| 390 | |||
| 391 | |||
| 392 | // ===================================================================== |
||
| 393 | // CARREGAR IDs JÁ EXISTENTES EM SINAIS.csv (para não duplicar) |
||
| 394 | // ===================================================================== |
||
| 395 | private void carregarIdsSinaisExistentes() { |
||
| 396 | Path path = Paths.get(ARQUIVO_SINAIS); |
||
| 397 | if (!Files.exists(path)) { |
||
| 398 | return; |
||
| 399 | } |
||
| 400 | |||
| 401 | try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { |
||
| 402 | String linha; |
||
| 403 | boolean primeira = true; |
||
| 404 | |||
| 405 | while ((linha = reader.readLine()) != null) { |
||
| 406 | if (primeira) { |
||
| 407 | primeira = false; |
||
| 408 | continue; // cabeçalho |
||
| 409 | } |
||
| 410 | if (linha.trim().isEmpty()) continue; |
||
| 411 | |||
| 412 | String[] p = linha.split(";", -1); |
||
| 413 | if (p.length < 1) continue; |
||
| 414 | |||
| 415 | String id = p[0].trim(); |
||
| 416 | if (!id.isEmpty()) { |
||
| 417 | idsSinaisGravados.add(id); |
||
| 418 | } |
||
| 419 | } |
||
| 420 | |||
| 421 | System.out.println("[ROBO-SINAIS] IDs de sinais existentes carregados: " + idsSinaisGravados.size()); |
||
| 422 | |||
| 423 | } catch (IOException e) { |
||
| 424 | System.err.println("[ROBO-SINAIS] Erro ao carregar IDs de SINAIS.csv: " + e.getMessage()); |
||
| 425 | e.printStackTrace(); |
||
| 426 | } |
||
| 427 | } |
||
| 428 | |||
| 429 | // ===================================================================== |
||
| 430 | // UTILS |
||
| 431 | // ===================================================================== |
||
| 432 | private BigDecimal parseBigDecimal(String s) { |
||
| 433 | if (s == null) return BigDecimal.ZERO; |
||
| 434 | s = s.trim(); |
||
| 435 | if (s.isEmpty()) return BigDecimal.ZERO; |
||
| 436 | return new BigDecimal(s.replace(",", ".")); |
||
| 437 | } |
||
| 438 | |||
| 439 | private String toStr(BigDecimal v) { |
||
| 440 | if (v == null) return ""; |
||
| 441 | return v.toPlainString().replace(".", ","); |
||
| 442 | } |
||
| 443 | } |