Indicadores en Java
Configura, compón y amplía el pipeline de indicadores en tiempo real de QTSurfer.
Todos los métodos de abajo están en InstrumentGroupRTIndicator y devuelven this para
encadenar. El nombre por defecto de un indicador (cuando se omite name) es el nombre del método
más los parámetros, por ejemplo rsi14.
Fuentes de precio
.addPrice() // precio de cierre → "price"
.add("bid", TickerValueSource.Bid)
.add("ask", TickerValueSource.Ask)
.add("vol", TickerValueSource.Volume)
// TickerValueSource: Bid, BidSize, Ask, AskSize, Open, High, Low, Close,
// Vwap, Volume, VolumeQuote, PercentChange, AutoAskClose Medias móviles
.sma(20) // SMA de 20 periodos → "sma20"
.sma("s20", 20) // nombre personalizado
.sma("s20", 20, false) // modo continuo (por defecto: discreto)
.sma("s20", "rsi14", 20) // SMA de otro indicador
.ema(9) // EMA de 9 periodos → "ema9"
.ema("rapida", 9)
.ema("rapida", "vol", 9) // EMA del volumen Medias, estadística y volatilidad
Gratuitos, en el mismo builder. Llamados sin un nombre propio, registran el nombre que indica el comentario.
.wma(5) // media móvil ponderada → "wma5"
.tema(4) // EMA triple → "tema4"
.hma(6) // media móvil de Hull → "hma6"
.mma(7) // → "mma7"
.kama() // adaptativa de Kaufman → "kama10_2_30"
.stdDev(5) // desviación típica → "stdDev5"
.variance(5) // → "variance5"
.volatility(5) // → "vol5"
.percentVolatility(5) // → "pvol5"
.bollingerPercentB() // %B de una banda de Bollinger → "pctB20_2"
.atr(14) // rango verdadero medio → "atr14" atr lee barras completas, igual que los indicadores pro: úsalo en una estrategia kline. En una estrategia ticker sus
valores no son los de la barra y no significan nada. wma y atr son métodos del builder, gratuitos en todos los planes;
ninguno de los dos tiene llamada en Pro.
Osciladores y momento
.rsi(14) // RSI de Cutler → "rsi14"
.rsi(14, false) // suavizado de Wilder
.rsi("miRsi", 14, true)
.cci(20) // → "cci20"
.macd() // MACD 12/26 de la fuente del grupo → "macd"
.macd("m", 8, 21) // periodos de EMA a medida
.stochRsi(14) // RSI estocástico, salida de 0 a 100 → "stochRsi14"
.bollinger("bb", 20, 2.0) // → "bb", "bbUpper", "bbLower"
.bollingerBandwidth("bb") // % de anchura de una banda de Bollinger Tasa de cambio y distancia
.percentChange("price") // % de cambio tick a tick
.rateChange("price") // tasa de cambio absoluta
.rateChange("rc", "price", true) // percent=true
.distanceMa("ema9") // % de distancia respecto a la MA
.distance("gap", "ema9", "ema21") // % de distancia entre dos indicadores Ganancia / pérdida / extremos
.gain("price") // periodos consecutivos de ganancia
.loss("price") // periodos consecutivos de pérdida
.gain("g", "price", false) // resetPeriodsOnSustain=false
.max("price") // máximo acumulado
.min("price") // mínimo acumulado
.sum("vol") // suma acumulada Aritmética
.add("spread", "ask", "bid") // spread = ask + bid
.diff("spread", "ask", "bid") // diff = ask - bid
.mul("price", 0.01) // escala por un coeficiente
.mul("ratio", "vol", "price") // vol * price
.fun("personalizada", "a", "b", (a, b) -> a / b) // BiFunction arbitraria Predicados y condicionales
.lessThan("sobrevendido", "rsi14", 30) // booleano: rsi14 < 30
.greatThan("sobrecomprado", "rsi14", 70)
.greatOrEqual("ge", "price", 50000)
.lessOrEqual("le", "price", 50000)
.equal("eq", "price", 100)
.notEqual("ne", "price", 100)
.predicate("personalizada", "price", v -> v > 0 && v < 100)
.periodCount("cnt", "sobrevendido", v -> v > 0) // cuenta periodos consecutivos en true Selección condicional
// Si indicador == coef → entoncesInd si no sinoInd
.equal("seleccionado", "senal", 1, "emaRapida", "emaLenta")
.conditional("salida", "bandera", ind -> ind.getValue() > 0, entoncesInd, sinoInd) Transformaciones
.clamp("price", 0.0, 100.0) // limita el valor a [min, max]
.clamp("price", v -> v < 0, 0.0) // aplica el clamp cuando el predicado es true
.round("price", 2) // redondea a N decimales
.decorate("price", "price", ind -> new MiEnvoltorio(ind)) Window listeners
.window("ema9", WindowTime.s1, listener) // se dispara cada 1 s
.window("ema9", Duration.ofSeconds(15), l) // duración personalizada
.window() // patrón builder
.windowTime(WindowTime.m5)
.indicator("rsi14")
.listener(myListener)
.build() Composición de indicadores (acceso de solo lectura)
Al construir un indicador personalizado que referencia a otro, usa una vista de solo lectura para evitar mutar el estado compartido. Dos enfoques equivalentes:
// Opción A — .ro() en cualquier instancia de RTIndicator (método por defecto de RTIndicator)
RTIndicator fuente = indicators.getExisting("ema9").ro();
indicators.add("personalizado", new MiIndicador(fuente));
// Opción B — getReadOnlyExisting() en el grupo de indicadores
RTIndicator fuente = indicators.getReadOnlyExisting("ema9");
indicators.add("personalizado", new MiIndicador(fuente));
// Opción C — getReadOnly() devuelve Optional (seguro si el indicador puede no existir)
indicators.getReadOnly("ema9").ifPresent(fuente ->
indicators.add("personalizado", new MiIndicador(fuente))); .ro() es un método por defecto del propio RTIndicator — disponible en cualquier instancia de
indicador sin pasar por el grupo. Úsalo cuando ya tienes una referencia al objeto indicador.
Los indicadores pro
Los indicadores pro vienen con los planes Pro y Elite. En Java son los métodos de Pro,
en com.wualabs.qtsurfer.engine.indicators.pro: Pro.on(indicators) recibe el grupo de indicadores que recibe setupIndicators, y cada llamada registra un indicador pro con los mismos nombres, formas e ids que lista la
tabla bajo “The pro indicators: uses” de la skill qtsurfer-qtscript-strategy (adx, cmf, garmanKlass, ichimoku, kurtosis, mfi, obv, parkinson, rogersSatchell, superTrend, vwap, yangZhang). Pro.on(indicators).adx(14) registra adx14 y, a su lado, adx14PlusDI y adx14MinusDI. Una ventana va sobre el principal (adx14), y el listener lee los demás por nombre.
Leen barras enteras, así que úsalos en una estrategia kline. Esta compra cuando el ADX dice que hay tendencia y la línea plus está por encima de la minus, y vende cuando está por debajo:
import com.wualabs.qtsurfer.engine.core.KlineInterval;
import com.wualabs.qtsurfer.engine.core.state.StateStore;
import com.wualabs.qtsurfer.engine.indicators.core.KlineValueSource;
import com.wualabs.qtsurfer.engine.indicators.helpers.WindowTimeRTIndicator.WindowTime;
import com.wualabs.qtsurfer.engine.indicators.helpers.group.InstrumentGroupRTIndicator;
import com.wualabs.qtsurfer.engine.indicators.pro.Pro;
import com.wualabs.qtsurfer.engine.strategy.AbstractKlineStrategy;
import com.wualabs.qtsurfer.engine.strategy.AbstractWindowListener;
public class AdxTrend extends AbstractKlineStrategy {
@Override
public KlineInterval getInterval() {
return KlineInterval.s1;
}
@Override
protected void setupIndicators(InstrumentGroupRTIndicator indicators) {
indicators.addPrice();
indicators.add("open", KlineValueSource.Open);
indicators.add("high", KlineValueSource.High);
indicators.add("low", KlineValueSource.Low);
indicators.add("close", KlineValueSource.Close);
indicators.add("volume", KlineValueSource.Volume);
Pro.on(indicators).adx(14);
indicators.window("adx14", WindowTime.m1, new TrendListener(this, indicators));
}
private class TrendListener extends AbstractWindowListener {
TrendListener(AbstractKlineStrategy strategy, InstrumentGroupRTIndicator indicators) {
super(strategy, indicators);
}
@Override
public void onChange(StateStore store, double prev, double actual) {
double close = indicators.getValue("close");
double plus = indicators.getValue("adx14PlusDI");
double minus = indicators.getValue("adx14MinusDI");
if (actual > 20 && plus > minus) emitBuy(close);
if (actual > 20 && plus < minus) emitSell(close);
}
}
} En una estrategia ticker, un indicador pro no se rechaza, pero sus valores no son los de la barra y no significan nada, y
validar o ejecutar la estrategia devuelve un aviso del motor que lo dice.
Registrar una estrategia que los usa se acepta en cualquier plan, y la respuesta de POST /strategy lleva requires: ["indicators.pro"]. Ejecutarla en un plan que no los incluye se rechaza con 402, al validarla, al lanzar un
backtest o un barrido con ella y al lanzar una ejecución en vivo con ella. El mensaje nombra la primera llamada que hace la
estrategia y la línea de tu fuente, aquí strategy uses pro indicators (Pro.adx, line 25); your plan: free. La comprobación
lee la estrategia compilada, así que cualquier otra mención de una clase del paquete pro cuenta igual, y entonces el mensaje
nombra la clase. El estado, el cuerpo y el upgradeUrl son los que describe
“The pro indicators and your plan” en la skill qtsurfer-qtscript-strategy.
Catálogo de indicadores avanzado (estadística y pro)
Más allá de los métodos del builder fluido de arriba, el motor incluye unas 150 clases de
indicadores en dos niveles, un subpaquete por categoría: com.wualabs.qtsurfer.engine.indicators.<category> para el nivel gratuito (averages, momentum, distance, bollinger, statistics, …) y com.wualabs.qtsurfer.engine.indicators.<category>.pro para el nivel pro (averages.pro, trend.pro, momentum.pro, volatility.pro, volume.pro, statistics.pro, …) — el segmento de paquete pro es siempre la marca del nivel pro. Las
clases pro vienen con los planes Pro y Elite, y no se distribuyen en el port OSS/TypeScript del motor. Son instancias RTIndicator normales — añade cualquiera de ellas por clase con .add("name", new XxxRTIndicator(...)), y
léelas con indicators.getValue("name").
Una estrategia enviada puede usar una clase pro. import com.wualabs.qtsurfer.engine.indicators.statistics.pro.ZScoreRTIndicator; compila, y la estrategia se registra en cualquier plan; lo que un plan sin los indicadores pro no puede hacer es
ejecutarla (consulta «Los indicadores pro» más arriba). La comprobación lee la estrategia compilada, así que cualquier
mención de una clase pro cuenta: un import que el código usa, un cast, un campo, una firma. El ejemplo de abajo
muestra la forma de la llamada; su última línea es pro, las demás son gratuitas.
import com.wualabs.qtsurfer.engine.indicators.statistics.StandardDeviationRTIndicator;
import com.wualabs.qtsurfer.engine.indicators.statistics.pro.ZScoreRTIndicator; // pro: necesita el plan Pro o Elite para ejecutarse
indicators
.addPrice() // "price"
.sma("media", 20)
.add("desv", new StandardDeviationRTIndicator(20)) // constructor (int periods)
.add("desvDe", new StandardDeviationRTIndicator( // constructor (RTIndicator, int periods)
indicators.getReadOnlyExisting("media"), 20))
.add("zscore", new ZScoreRTIndicator(/* ver la clase para el constructor */)); // pro: necesita el plan Pro o Elite para ejecutarse Los constructores varían según la clase — la mayoría acepta (int periods) y/o (RTIndicator source, int periods); algunos (construidos con lombok) difieren, así que consulta la clase.
Las clases por categoría, con el paquete en el que vive cada una (bajo com.wualabs.qtsurfer.engine.indicators) —
Tendencia, Volumen y los ratios de rendimiento son solo pro hoy, el nivel gratuito todavía no tiene ningún indicador en
esas categorías. El nombre de una clase es el de la tabla más RTIndicator (Sma es SmaRTIndicator); el mismo nombre
corto puede existir en un paquete gratuito y en uno pro (StandardDeviation, Variance), así que importa el del paquete
que quieras:
| Categoría | Nivel | Paquete | Clases *RTIndicator |
|---|---|---|---|
| Medias móviles | Gratuito | averages | Sma, Ema, Wma, WmaIncremental, Hma, Kama, Tema, Mma |
| Medias móviles | Pro | averages.pro | Alma, Dema, Envelopes, Frama, LeastSquaresMovingAverage, McGinleyDynamic, Smma |
| Bandas | Gratuito | bollinger | BollingerBandWidth, BollingerBands, BollingerPercentB |
| Osciladores | Gratuito | momentum | Rsi, Macd, StochasticRsi, Cci |
| Osciladores | Pro | momentum.pro | AcceleratorOscillator, AdaptiveFisherTransform, AwesomeOscillator, BalanceOfPower, BullBearPower, ChandeForecastOscillator, Cmo, ConnorsRsi, CoppockCurve, DetrendedPriceOscillator, FisherTransform, KnowSureThing, Momentum, RelativeVigorIndex, Roc, StochasticOscillator, Trix, TrueStrengthIndex, UltimateOscillator, WilliamsR |
| Estadística | Gratuito | statistics | StandardDeviation, Variance |
| Estadística | Pro | statistics.pro | Autocorrelation, Beta, Correlation, Covariance, HurstExponent, Kurtosis, LinearRegressionSlope, RollingPercentile, SimpleLinearRegression, Skewness, StandardDeviation, ValueAtRisk, Variance, ZScore |
| Volatilidad | Gratuito | volatility | Atr |
| Volatilidad | Gratuito | helpers | Volatility, PercentVolatility |
| Volatilidad | Pro | volatility.pro | ChoppinessIndex, KeltnerChannel, MassIndex, Natr, RelativeVolatilityIndex |
| Volatilidad (estimadores) | Pro | statistics.pro.volatility | EwmaVolatility, GarmanKlassVolatility, ParkinsonVolatility, RealizedVolatility, RogersSatchellVolatility, YangZhangVolatility |
| Tendencia | Solo pro | trend.pro | Adx, Aroon, ChandeKrollStop, ChandelierExit, DonchianChannel, EfficiencyRatio, Ichimoku, ParabolicSar, SuperTrend, Vortex |
| Volumen | Solo pro | volume.pro | Adl, ChaikinOscillator, Cmf, EaseOfMovement, ElderForceIndex, KlingerOscillator, Mfi, NegativeVolumeIndex, Obv, Pvt, Vwap |
| Ratios (rendimiento) | Solo pro | statistics.pro.ratios | CalmarRatio, InformationRatio, MaxDrawdown, OmegaRatio, SharpeRatio, SortinoRatio, TailRatio, TreynorRatio, UlcerIndex |
| Distancia y cambio | Gratuito | distance | Distance, DistanceFromMa |
| Distancia y cambio | Gratuito | helpers | RateChange, PercentChangePeriod |
| Conteo y extremos | Gratuito | helpers | Gain, Loss, Max, Min, Count |
| Conteo y extremos | Gratuito | numeric | Sum |
| Derivados (los registra el builder) | Gratuito | numeric | BiFunction, Clamp, ClampPredicate, Conditional, Predicate, Round |
| Derivados (los registra el builder) | Gratuito | helpers | PredicatePeriodCounter |
Compón estas clases alimentando la vista de solo lectura de un indicador al constructor (RTIndicator, …) de otro (por ejemplo, un ZScore de una Sma). Así se hacen estadísticas
móviles o agregaciones sin reinventar la rueda en update().
Escribir un RTIndicator personalizado
Cuando ningún indicador integrado encaja, implementa la interfaz RTIndicator (com.wualabs.qtsurfer.engine.indicators.core.RTIndicator) — o extiende AbstractRTIndicator para el andamiaje común:
import com.wualabs.qtsurfer.engine.indicators.core.RTIndicator;
public class MiIndicador implements RTIndicator {
private double valor;
private boolean listo;
@Override public double getValue() { return valor; }
@Override public double update(double nuevoValor) { // se llama una vez por tick con el valor de origen
this.valor = /* calcula de forma incremental a partir de nuevoValor */ nuevoValor;
this.listo = true;
return valor;
}
@Override public boolean isReady() { return listo; } // controla el calentamiento (por defecto true)
@Override public void reset() { valor = 0; listo = false; } // de Resettable
} Regístralo como cualquier indicador integrado: indicators.add("miInd", new MiIndicador()). La
interfaz es pequeña: getValue() (salida actual), update(double) (incremental, por tick), isReady() (puerta de calentamiento, por defecto true), reset(). update(Number) / update(RTIndicator) y ro() (vista de solo lectura) vienen como métodos por defecto, gratis.
Indicadores enriquecidos (acceso al snapshot completo)
Los indicadores escalares reciben un único double (un campo, extraído vía TickerValueSource). Cuando un indicador necesita más de un campo — OHLC para el ATR,
volumen+precio para VWAP/OBV, bid/ask para microestructura — implementa RichRTIndicator<T extends MarketSnapshot> en su lugar y lee el snapshot completo:
import com.wualabs.qtsurfer.engine.indicators.core.RichRTIndicator;
import com.wualabs.qtsurfer.engine.core.MarketSnapshot;
public class MiIndicadorOhlc implements RichRTIndicator<MarketSnapshot> {
private double valor;
@Override public double updateFrom(MarketSnapshot instantanea) { // instantánea completa: O/H/L/C/V, bid/ask
this.valor = /* combina varios campos */ 0;
return valor;
}
@Override public double getValue() { return valor; }
@Override public double update(double v) { return valor; } // ruta escalar sin usar
@Override public void reset() { valor = 0; }
} El motor construye el snapshot una vez por tick y lo despacha a cada RichRTIndicator registrado, mientras los indicadores escalares siguen recibiendo su campo extraído. Se registra
igual: indicators.add("miOhlc", new MiIndicadorOhlc()).
Indicadores ocultos
Prefíjalos con _ para excluirlos de los metadatos de reporte de señales:
.gain("_gananciaCruda", "price") // uso interno, no se reporta Es azúcar sintáctico sobre la entrada de metadatos VISIBILITY — ver más abajo.
Metadatos de indicador
Todo indicador lleva pequeños metadatos descriptivos clave/valor sobre sí mismo, separados de su
nombre de búsqueda registrado, legibles desde cualquier instancia de RTIndicator (por ejemplo,
vía indicators.getExisting("name") / getReadOnlyExisting("name")):
RTIndicator ind = indicators.getExisting("gap");
ind.getId(); // id de tipo canónico, p. ej. "distance", "bollinger", "rsi"
ind.getDisplayHint(); // DisplayHint: ABSOLUTE (por defecto), PERCENT o VOLUME
ind.isHidden(); // true si es solo interno (el prefijo "_" de arriba lo activa)
ind.getMeta().get("periods"); // cualquier otra clave descriptiva, o null si no está definida Útil para introspección sin analizar el nombre como cadena de texto — por ejemplo, comprobar getDisplayHint() == DisplayHint.PERCENT antes de formatear un valor para mostrarlo, o getId() para bifurcar de forma genérica según qué indicador esté registrado bajo un nombre. distance() / percentChange() / distanceMa() fijan PERCENT automáticamente; la mayoría del resto de
indicadores no lleva metadatos — getMeta() devuelve el IndicatorMeta.EMPTY compartido, nunca null.
Para adjuntar metadatos a un indicador personalizado (consulta Escribir un RTIndicator
personalizado), extiende AbstractRTIndicator y usa sus
setters fluidos en el registro:
import com.wualabs.qtsurfer.engine.indicators.core.AbstractRTIndicator;
import com.wualabs.qtsurfer.engine.indicators.core.IndicatorMeta;
import com.wualabs.qtsurfer.engine.indicators.core.DisplayHint;
indicators.add("gap",
new MiIndicadorDistancia(a, b)
.withMeta(IndicatorMeta.ID, "distance")
.withMeta("periods", 20)
.withDisplayHint(DisplayHint.PERCENT)); Los metadatos son un descriptor de solo escritura — un indicador nunca debe leer sus propios metadatos para dirigir su cálculo, eso lo convertiría en un segundo canal de configuración no declarado. Fíjalos una sola vez al registrar; léelos solo desde fuera.