Blame |
Last modification |
View Log
| Download
| RSS feed
package br.com.robo.model;
import br.com.robo.model.Candle;
import java.util.*;
import java.util.stream.Collectors;
public class SwingLevelDetector
{
public enum LevelType
{
HIGH, LOW
}
public static class Level {
private final int index
;
private final double price
;
private final LevelType type
;
// flag interno
private boolean mitigated
;
public Level(int index,
double price, LevelType type
) {
this.
index = index
;
this.
price = price
;
this.
type = type
;
this.
mitigated =
false;
}
public int getIndex
() {
return index
;
}
public double getPrice
() {
return price
;
}
public LevelType getType
() {
return type
;
}
boolean isMitigated
() {
return mitigated
;
}
void setMitigated
(boolean mitigated
) {
this.
mitigated = mitigated
;
}
@
Override
public String toString
() {
return "Level{" +
"index=" + index +
", price=" + price +
", type=" + type +
'}';
}
}
/**
* Detecta fundos e topos predominantes:
* - encontra pivôs (high > vizinhos = topo, low < vizinhos = fundo)
* - forma pares adjacentes (topo-fundo ou fundo-topo)
* - para cada par, calcula a linha média (50%)
* - se depois do par houver:
* * rompimento da linha média (50%), OU
* * rompimento do topo, OU
* * rompimento do fundo,
* o par é considerado mitigado e seus níveis são descartados
* - retorna apenas níveis NÃO mitigados (predominantes)
*/
public List<Level> detectPredominantLevels
(List<Candle
> candles
) {
List<Level> pivots = detectSwings
(candles
);
if (pivots.
size() < 2) {
return Collections.
emptyList();
}
for (int i =
0; i
< pivots.
size() -
1; i++
) {
Level a = pivots.
get(i
);
Level b = pivots.
get(i +
1);
double topPrice
;
double bottomPrice
;
if (a.
getType() == LevelType.
HIGH && b.
getType() == LevelType.
LOW) {
topPrice = a.
getPrice();
bottomPrice = b.
getPrice();
} else if (a.
getType() == LevelType.
LOW && b.
getType() == LevelType.
HIGH) {
topPrice = b.
getPrice();
bottomPrice = a.
getPrice();
} else {
// dois topos seguidos ou dois fundos seguidos → não formam um par topo–fundo ou fundo–topo
continue;
}
double mid =
(topPrice + bottomPrice
) /
2.0;
int lastIdx =
Math.
max(a.
getIndex(), b.
getIndex());
boolean mitigatedPair =
false;
for (int j = lastIdx +
1; j
< candles.
size(); j++
) {
Candle c = candles.
get(j
);
boolean rompeMid =
(c.
getHigh() >= mid
&& c.
getLow() <= mid
);
boolean rompeTopo = c.
getHigh() > topPrice
;
boolean rompeFundo = c.
getLow() < bottomPrice
;
if (rompeMid || rompeTopo || rompeFundo
) {
mitigatedPair =
true;
break;
}
}
if (mitigatedPair
) {
a.
setMitigated(true);
b.
setMitigated(true);
}
}
// ✅ Só devolve os níveis que nunca foram mitigados em nenhum par
return pivots.
stream()
.
filter(l -
> !l.
isMitigated())
.
collect(Collectors.
toList());
}
/**
* Detecta pivôs simples:
* - topo: high[i] > high[i-1] e high[i] > high[i+1]
* - fundo: low[i] < low[i-1] e low[i] < low[i+1]
*/
private List<Level> detectSwings
(List<Candle
> candles
) {
List<Level> levels =
new ArrayList<>();
for (int i =
1; i
< candles.
size() -
1; i++
) {
Candle prev = candles.
get(i -
1);
Candle curr = candles.
get(i
);
Candle next = candles.
get(i +
1);
if (curr.
getHigh() > prev.
getHigh() && curr.
getHigh() > next.
getHigh()) {
levels.
add(new Level(i, curr.
getHigh(), LevelType.
HIGH));
} else if (curr.
getLow() < prev.
getLow() && curr.
getLow() < next.
getLow()) {
levels.
add(new Level(i, curr.
getLow(), LevelType.
LOW));
}
}
return levels
;
}
}