roboquant avatar

Technical Analysis

Introduction

Technical analysis is based on information coming directly from the financial markets and exchanges. This includes attributes like:

  • Asset prices, including quotes, trades and aggregates like price-bars

  • Volumes of trades and aggregates there of

  • Open interest (only for certain types of asset classes)

  • Shorting percentage

  • Spread (difference between ask and bid prices)

Since many strategies use technical analysis as a basis for decision-making, roboquant comes out-of-the-box with support for:

This page goes into more details on how to use the TaLib library.

Basic usage

TaLib is a fast and powerful library that can be used for calculating many different types of indicators. It is a pure Java library, but it is exposing an API that might be confusing at first.

To make it more convenient to use it, roboquant comes with a wrapper for it. The following example shows how to use it:

val taLib = TaLib()
val asset = Stock("TEST")

// Keep maximum 30 price-bars in history
val series = PriceBarSeries(30)
repeat(30) {
    series.add(PriceBar(asset, 100.0, 101.0, 99.0, 100.0, 1000), Instant.now())
}

// Calculate EMA over the last 10 days using the default price-type (close)
val ema10: Double = taLib.ema(series, 10)

// Calculate EMA over the last 5 days using the low-prices of the price-bars
val ema5: Double = taLib.ema(series.low, 5)

// Calculate RSI over the default period (14) using the default price-type (close)
val rsi: Double = taLib.rsi(series)

// Did we detect the Two Crow candlestick pattern today?
val found: Boolean = taLib.cdl2Crows(series)

// Did we detect the Two Crow candlestick pattern 4 days ago?
val found2: Boolean = taLib.cdl2Crows(series, 4)

// Some indicators return multiple double values, like the Bollinger Bands
val (high, mid, low) = taLib.bbands(series)

If you invoke a taLib-indicator and don’t have enough data to perform the calculations, an InsufficientData exception will be thrown. You can either increase the initial capacity of the PriceBarSerie, or use this exception to increase the historic data capacity:

if (series.isFull()) {
    try {
        taLib.sma(series, 30)
    } catch (err: InsufficientData) {
        // The error contains the minimum required size
        series.increaseCapacity(err.minSize)
    }
}

TaLibStrategy

If you use TaLibStrategy as a foundation for your strategy, much of the boilerplate code is already taken care of. All you have to do is to define the sell and buy rules.

Each rule should return true or false. In case you return true, a corresponding Signal will be created. So if you return true from the buy-rule, a buy signal will be created.

If you don’t implement a rule, it will by default always return false.

You can provide the amount of history to keep track of when initializing the TaLibStrategy. But if you don’t, the history will grow automatically until the rules can be successfully executed.

val strategy = TaLibStrategy()

// Define the buy rule
strategy.buy {
    cdl3StarsInSouth(it) || cdl3WhiteSoldiers(it)
}

// Define the sell rule
strategy.sell {
    ema(it.close, 5) < ema(it.close, 10)
}

You can also write your own TaLib indicators and use them in a strategy. A TaLib indicator is just a plain Kotlin function with TaLib as its receiver:

// Your indicator
fun TaLib.myIndicator(period: Int = 10, series: PriceBarSeries): Double {
    val x1 = sma(series, period)
    val x2 = ema(series, period)
    return x1 - x2
}

// Use the indicator in a strategy
val s = TaLibStrategy()
s.buy {
    myIndicator(10, it) > 0
}

If you want to run different indicators over different time-spans, that is also possible using the aggregate function:

fun TaLib.myIndicator(series1M: PriceBarSeries): Double {
    val series5M = series1M.aggregate(5)
    val x1 = sma(series1M, 20)
    val x2 = ema(series5M, 2)
    return x1 - x2
}

// You have to specify enough history for both 1M and 5M
val s = TaLibStrategy(21)
s.buy {
    myIndicator(it) > 0
}

You also have access to the time the price-bars were recorded:

val s = TaLibStrategy()
val zoneId = ZoneId.of("America/New_York")
s.buy {

    it.timeline // List<Instant>

    // Get only the latest time
    val now = it.now()
    val localDateTime = now.atZone(zoneId)

    // Don't BUY after 4:00 PM
    if (localDateTime.hour < 16) {

        // Use the data from the last 60 minutes
        val timeFrame = Timeframe(now - 60.minutes, now, true)
        val series = it[timeFrame]
        myIndicator(series)
    } else {
        false
    }
}

TaLibMetric & TaLibIndicator

Similar to the TaLibStrategy, the TaLibMetric makes it convenient to turn TaLib indicators into roboquant metrics.

The following example demonstrates how to create a metric for the three values of the Bollinger Band indicator.

val metric = TaLibMetric {
    val (h, m, l) = bbands(it)
    mapOf(
        "bbands.low" to l,
        "bbands.mid" to m,
        "bbands.high" to h,
    )
}

The final names of the metrics will be in the form of "<metric name>.<symbol>".

The TaLibIndicator works almost exactly the same, but as usual, an indicator only works on a single price-series.

The following indicator flags when it detects a 3-white-soldiers candlestick pattern:

val metric = TaLibIndicator {
    val value = if (cdl3WhiteSoldiers(it)) 1.0 else 0.0
    mapOf(
        "threewhitesoldiers" to value,
    )
}