Why You Can't Calculate RSI On-Chain
RSI requires 14 periods of historical prices. For daily RSI, that's 14 days of data. For hourly, 14 hours.
Storing and computing this in Solidity would cost thousands in gas per calculation. Floating-point math doesn't exist in EVM. It's not viable.
The practical solution: deliver the pre-calculated value via oracle — computed off-chain, delivered on-chain through Chainlink.
How Pythia Feeds Work
Pythia uses Chainlink's Direct Request model (not the AggregatorV3Interface used by price feeds). The pattern is:
- Your contract calls
requestFeed("bitcoin_RSI_1D_14")and pays LINK - Pythia's off-chain engine computes the indicator from 4 redundant data sources
- The Chainlink Operator delivers the result to your contract's
fulfill()callback - Your contract stores the value and acts on it
This is two transactions, not a view call — the same async pattern used by Chainlink VRF and Any API.
The Indicators Available On-Chain
Pythia delivers pre-calculated technical indicators for 21 tokens across 4 timeframes (5M, 1H, 1D, 1W):
- EMA (20 and 50 periods)
- RSI (14 periods)
- Bollinger Bands (upper and lower, 20-period, 2 std dev)
- Volatility (24H)
- VWAP
- Liquidity
That's 462 indicator instances on Polygon mainnet, all delivered through Chainlink.
Reading a Single Indicator (Discovery Tier)
The Discovery tier returns one indicator per request for 0.01 LINK. This is Pythia's actual deployed contract — verified on Polygonscan:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";
import "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
contract MyIndicatorReader is ChainlinkClient, ConfirmedOwner {
using Chainlink for Chainlink.Request;
uint256 public lastValue;
string public lastFeed;
bytes32 private jobId;
uint256 private fee;
event FeedRequested(bytes32 indexed requestId, string feed);
event FeedFulfilled(bytes32 indexed requestId, uint256 value);
constructor(
address _link,
address _oracle,
bytes32 _jobId
) ConfirmedOwner(msg.sender) {
_setChainlinkToken(_link);
_setChainlinkOracle(_oracle);
jobId = _jobId;
fee = (1 * LINK_DIVISIBILITY) / 100; // 0.01 LINK
}
/// @notice Request any single indicator from Pythia
/// @param feed Feed name, e.g. "bitcoin_RSI_1D_14", "aave_EMA_5M_20"
function requestFeed(string memory feed) public onlyOwner returns (bytes32) {
Chainlink.Request memory req = _buildChainlinkRequest(
jobId, address(this), this.fulfill.selector
);
req._add("feed", feed);
bytes32 requestId = _sendChainlinkRequest(req, fee);
lastFeed = feed;
emit FeedRequested(requestId, feed);
return requestId;
}
/// @notice Chainlink callback — stores the indicator value
/// @param _value The indicator value with 18 decimal places
function fulfill(
bytes32 _requestId,
uint256 _value
) public recordChainlinkFulfillment(_requestId) {
lastValue = _value;
emit FeedFulfilled(_requestId, _value);
}
function withdrawLink() external onlyOwner {
LinkTokenInterface link = LinkTokenInterface(_chainlinkTokenAddress());
require(link.transfer(msg.sender, link.balanceOf(address(this))));
}
}
Deploy, fund with LINK, call requestFeed("bitcoin_RSI_1D_14"). The result arrives in fulfill() — scaled by 1e18 (so RSI of 42.72 comes back as 42720000000000000000).
A Real Use Case: RSI-Gated Vault Deposits
Yield vaults typically accept deposits at any time. But depositing during extreme oversold conditions (RSI below 25) often means buying into a temporary panic — the market recovers and the vault captures the rebound.
A vault can use Pythia's RSI feed to only accept deposits when conditions are favorable:
contract RSIGatedVault is ChainlinkClient, ConfirmedOwner {
using Chainlink for Chainlink.Request;
uint256 public latestRSI;
bool public depositsOpen;
// RSI thresholds (scaled by 1e18)
uint256 public constant RSI_OPEN = 40 * 1e18; // Open deposits below RSI 40
uint256 public constant RSI_CLOSE = 70 * 1e18; // Close deposits above RSI 70
bytes32 private jobId;
uint256 private fee;
constructor(address _link, address _oracle, bytes32 _jobId) ConfirmedOwner(msg.sender) {
_setChainlinkToken(_link);
_setChainlinkOracle(_oracle);
jobId = _jobId;
fee = (1 * LINK_DIVISIBILITY) / 100;
}
/// @notice Keeper calls this periodically to refresh RSI
function refreshRSI() external returns (bytes32) {
Chainlink.Request memory req = _buildChainlinkRequest(
jobId, address(this), this.fulfillRSI.selector
);
req._add("feed", "bitcoin_RSI_1D_14");
return _sendChainlinkRequest(req, fee);
}
function fulfillRSI(bytes32 _requestId, uint256 _value)
public recordChainlinkFulfillment(_requestId)
{
latestRSI = _value;
// Automatically gate deposits based on RSI
if (_value < RSI_OPEN) {
depositsOpen = true;
} else if (_value > RSI_CLOSE) {
depositsOpen = false;
}
}
function deposit() external payable {
require(depositsOpen, "Deposits closed — RSI too high");
// ... vault deposit logic
}
}
The keeper calls refreshRSI() periodically. When RSI drops below 40 — indicating oversold conditions — deposits open automatically. When RSI rises above 70 — overbought — deposits close. No off-chain server needed for the decision logic.
Another Pattern: EMA Crossover Signal
EMA crossovers (short-term EMA crossing above long-term EMA) are one of the most common trading signals. With Pythia's bundle tier, you can get both EMAs in a single on-chain call:
/// @notice Request all indicators for a token (Analysis tier — 0.03 LINK)
function requestBundle(string memory token) public onlyOwner returns (bytes32) {
Chainlink.Request memory req = _buildChainlinkRequest(
bundleJobId, address(this), this.fulfillBundle.selector
);
req._add("token", token);
req._add("mode", "bundle");
return _sendChainlinkRequest(req, bundleFee);
}
function fulfillBundle(bytes32 _requestId, uint256[] memory _values)
public recordChainlinkFulfillment(_requestId)
{
// Bundle slot [2] = EMA 1H (20-period)
// Bundle slot [13] = EMA 1D (20-period)
// Bundle slot [14] = EMA 1W (20-period)
uint256 ema1d20 = _values[13];
// Compare with EMA 50 from a separate Discovery request, or
// use the 1H vs 1D EMA spread as a momentum proxy
lastBundle = _values;
}
The bundle returns all indicators for a token in one call. Slot positions are stable — new indicators are appended, never reordered.
Live Data — What the Feeds Return Right Now
Here's what Pythia returns on-chain as of April 2, 2026 (values from most recent oracle fulfillment):
| Token | Feed Name | What It Returns |
|---|---|---|
| BTC | bitcoin_RSI_1D_14 |
Daily RSI (14-period) x 1e18 |
| BTC | bitcoin_EMA_1D_20 |
20-day EMA price x 1e18 |
| BTC | bitcoin_EMA_1D_50 |
50-day EMA price x 1e18 |
| BTC | bitcoin_BOLLINGER_1D_UPPER |
Upper Bollinger Band x 1e18 |
| SOL | solana_RSI_1H_14 |
Hourly RSI x 1e18 |
| AAVE | aave_EMA_5M_20 |
5-minute EMA x 1e18 |
Feed names follow the pattern: {engine_id}_{INDICATOR}_{TIMEFRAME}_{PARAM}
21 tokens available including BTC, SOL, AAVE, UNI, SUI, TAO, RENDER, ONDO, and more.
Try It Free (No Signup Required)
Pythia runs a free faucet on Polygon mainnet — PythiaFaucet. No LINK needed, 5 requests per day per address. Call requestIndicator("bitcoin_RSI_1D_14") directly on Polygonscan.
For AI-assisted development:
pip install pythia-oracle-mcp
Use with Claude, Cursor, Windsurf, or any MCP-compatible AI tool. Ask your assistant "What RSI feeds does Pythia have for Bitcoin?" — it calls get_token_feeds("bitcoin") and returns every available feed name, timeframe, and contract address.
Solidity examples:
Full contract examples at github.com/pythia-the-oracle/pythia-oracle-examples
Summary
- You cannot calculate RSI, EMA, or Bollinger Bands on-chain economically
- Pythia delivers pre-calculated indicators via Chainlink's Direct Request pattern
- Your contract sends a request + LINK, receives the value in a
fulfill()callback - All values scaled by 1e18. Feed names:
{engine_id}_{INDICATOR}_{TIMEFRAME}_{PARAM} - Real use cases: RSI-gated vaults, EMA crossover signals, volatility circuit breakers
- Free faucet on Polygon mainnet — no signup, no LINK needed
The feeds are live. The only thing left is what you build with them.
Explore feeds: pip install pythia-oracle-mcp | Docs: pythia.c3x-solutions.com
Top comments (0)