# RSI Safe (RSI_SAFE)

> Checked StratForge C++23 API, calculation and warm-up behavior.

**Route**: `/indicators/rsi_safe/`

Safe division handles a zero average loss; it does not detect price/RSI divergence. Output is on the 0–100 RSI scale with default safety values, not a return percentage.

`change = close[t] - close[t - lookback]`

`gain = max(change, 0)`; `loss = max(-change, 0)`

`average[t] = (average[t - 1] * (period - 1) + value[t]) / period`

`RS = average_gain / average_loss`; `RSI = 100 - 100 / (1 + RS)`

## Parameters

- `period = 14`
- `movav = stratforge::RsiMovAv::Smoothed` (`Simple`, `Exponential`)
- `safehigh = 100.0`
- `safelow = 50.0`
- `lookback = 1`

`RSI_Safe`: `safediv = true`; `RSI`: `safediv = false`.
`upperband` / `lowerband`: 70 / 30 — Chart thresholds, not constructor arguments.

## Worked example

For period = 3 and lookback = 1, closes [10, 11, 10, 11, 13] give changes [1, -1, 1, 2]. Initial mean gain = 2/3 and mean loss = 1/3, so RSI on bar 4 = 66.666667. On bar 5, smoothed gain = 10/9 and loss = 2/9, so RSI = 83.333333. The first three bars produce NaN. Warm-up is period + lookback (15 bars by default).

With default safety values, a rising series with zero losses gives 100, a falling series with zero gains gives 0, and a flat series (0/0) gives 50. These branches apply only after warm-up. Custom safehigh/safelow replace the zero-denominator results; they are RSI values, not denominator epsilons.

Use finite inputs and positive periods/lookback. This revision normalizes zero period/lookback to 1; it does not validate custom safety values or promise meaningful output for non-finite prices. These illustrative calculations are not trading signals or evidence of returns.

## C++23 API — StratForge

```cpp
#include <stratforge/indicators/rsi.hpp>
#include <cassert>
#include <cmath>
#include <iomanip>
#include <iostream>

int main() {
    stratforge::Line<double> close, rising, falling, flat;
    stratforge::RSI_Safe rsi(close, 3);
    stratforge::RSI_Safe up(rising, 3), down(falling, 3), neutral(flat, 3);
    assert(rsi.minimum_period() == 4);
    std::cout << std::fixed << std::setprecision(6);
    for (double price : {10.0, 11.0, 10.0, 11.0, 13.0}) {
        close.forward(price);
        rsi.next();
        if (close.size() < 4) assert(std::isnan(rsi[0]));
        else std::cout << rsi[0] << " ";
    }
    assert(std::abs(rsi.line()[-1] - 66.666666667) < 1e-9);
    assert(std::abs(rsi[0] - 83.333333333) < 1e-9);
    for (int i = 0; i < 4; ++i) {
        rising.forward(10.0 + i);
        falling.forward(10.0 - i);
        flat.forward(10.0);
        up.next();
        down.next();
        neutral.next();
    }
    assert(up[0] == 100.0 && down[0] == 0.0 && neutral[0] == 50.0);
    std::cout << up[0] << " " << down[0] << " " << neutral[0] << "\n";
}
```

## Reproduce

StratForge `d25404dcf2c3842ba304ddc60ac19cfec1f8aa37` (2026-08-28); GCC 14, C++23; scalar build, no xsimd dependency.

```sh
git clone https://github.com/StratCraftsAI/StratForge.git StratForge
git -C StratForge checkout --detach d25404dcf2c3842ba304ddc60ac19cfec1f8aa37
g++-14 -std=c++23 -O2 -Wall -Wextra -pedantic -IStratForge/include example.cpp -o example
./example
```

`example.cpp`: C++23 API. stdout:

```text
66.666667 83.333333 100.000000 0.000000 50.000000
```

## Sources

- [StratForge rsi.hpp](https://github.com/StratCraftsAI/StratForge/blob/d25404dcf2c3842ba304ddc60ac19cfec1f8aa37/include/stratforge/indicators/rsi.hpp)
- [StratForge Line](https://github.com/StratCraftsAI/StratForge/blob/d25404dcf2c3842ba304ddc60ac19cfec1f8aa37/include/stratforge/core/line.hpp)
