Skip to content

chart scripting for pro dashboard #1931

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open

chart scripting for pro dashboard #1931

wants to merge 9 commits into from

Conversation

ReynardoEW
Copy link
Member

@ReynardoEW ReynardoEW commented Jun 19, 2025

LlamaScript Documentation

1. Overview

LlamaScript is a simple, powerful scripting language designed for fetching, analyzing, and visualizing financial data from the DeFiLlama ecosystem. It allows users to perform complex calculations, compare metrics across protocols and chains, and generate custom charts directly from a text-based input.

The core of LlamaScript is built around:

  • Data Fetching: Easily query time-series data for protocols, chains, and tokens.
  • Element-wise Operations: Perform arithmetic on entire datasets as if they were single numbers.
  • Analytical Functions: A rich library of financial and statistical functions like moving averages, percentage changes, and normalization.
  • Declarative Plotting: A simple plot() command to visualize any resulting data series.

2. Basic Syntax

Variables

You can assign any expression to a variable for later use. This is useful for keeping your scripts clean and avoiding re-fetching the same data.

// Assign the TVL of Uniswap to a variable
uniswap_tvl = protocol('uniswap-v3').tvl

// Use the variable in a calculation
plot(ma(uniswap_tvl, 30))

Plotting

The primary way to visualize data is with the plot() function.

plot(series, [label], [chartType], [color], [dashed], [group])

  • series: The time-series data to plot. This is the only required argument.
  • label (optional string): The name that appears in the chart legend.
  • chartType (optional string): Type of chart. Supports 'area' (default) or 'bar'.
  • color (optional string): A valid CSS color (e.g., '#ff0000', 'red').

Example:

// Plot Aave's TVL as a blue bar chart
plot(protocol('aave-v2').tvl, 'Aave TVL', 'bar', '#1f77b4')

Arithmetic Operations

Standard arithmetic operators (+, -, *, /) can be used between two time-series, or a time-series and a number. The operation is performed "element-wise" for each timestamp.

Example:

// Calculate the ratio of fees to revenue for GMX
fees_to_revenue = protocol('gmx').fees / protocol('gmx').revenue
plot(fees_to_revenue, 'GMX Fees/Revenue Ratio')

// Add 100 to every value in the series
plot(token('bitcoin').price + 100, 'Bitcoin Price + $100')

3. Data Fetching

Data is fetched using dedicated functions that return a special "Llama Entity". You must then access a property on this entity to get the time-series data.

protocol(name, [chainName])

Fetches a protocol entity.

  • name: The protocol's slug (e.g., 'aave-v2', 'uniswap-v3').
  • chainName (optional): The slug of a chain (e.g., 'ethereum', 'arbitrum') to get data for that specific deployment.

Available Properties:

  • .tvl, .volume, .fees, .revenue, .mcap, .price, .medianApy

Example:

// Get total TVL for Lido
plot(protocol('lido').tvl)

// Get Aave v3's TVL only on Arbitrum
plot(protocol('aave-v3', 'arbitrum').tvl)

chain(name)

Fetches a chain entity.

  • name: The chain's slug (e.g., 'ethereum', 'solana').

Available Properties:

  • .tvl, .volume, .fees, .revenue, .users, .txs, .activeUsers, .newUsers, .gasUsed
  • DEX-specific: .aggregators, .perps, .bridgeAggregators, .perpsAggregators, .options
  • Other: .bribes, .tokenTax

Example:

// Plot the daily active users on the Polygon chain
plot(chain('polygon').activeUsers, 'Polygon Daily Active Users')

token(id)

Fetches a token entity.

  • id: The token's Coingecko ID (e.g., 'bitcoin', 'ethereum').

Available Properties:

  • .price, .volume, .marketCap

Example:

// Plot the price of Chainlink
plot(token('chainlink').price, 'LINK Price')

4. Analytical Functions

These functions take one or more arguments (often a time-series) and return a new, transformed time-series.

Function Description Example
ma(series, window) Simple Moving Average. plot(ma(token('bitcoin').price, 50))
ema(series, window) Exponential Moving Average. plot(ema(protocol('gmx').fees, 14))
DIFF(series) Difference between consecutive values. plot(DIFF(chain('ethereum').fees))
abs(series) Absolute value of each data point. plot(abs(RETURNS(token('solana').price)))
pctChange(series) Percentage change between consecutive values. plot(pctChange(protocol('aave-v2').tvl))
RETURNS(series, [type]) 'simple' (default) or 'log' returns. plot(RETURNS(token('ethereum').price, 'log'))
cumSum(series) Cumulative sum over time. plot(cumSum(protocol('uniswap-v3').revenue))
lag(series, n) Lags the series by n periods. plot(lag(token('bitcoin').price, 7))
zscore(series) Z-score (standard score) of the entire series. plot(zscore(protocol('dydx').volume))
normalize(series) Scales the series to a 0-1 range. plot(normalize(protocol('aave-v2').tvl))
drawdown(series) The drawdown from the series' all-time high. plot(drawdown(token('bitcoin').price))
rolling(series, window, func) Rolling window calculation. func can be 'mean', 'sum', or 'stddev'. plot(rolling(chain('ethereum').fees, 30, 'stddev'))
resample(series, interval) Resamples data to a new time interval (in seconds). Aggregates by averaging. plot(resample(token('bitcoin').price, 604800)) (weekly)
clip(series, min, max) Limits the values in a series to be within a min-max range. plot(clip(token('bitcoin').price, 20000, 30000))
if(condition, then, else) If condition is true, returns then, otherwise else. Works element-wise. p = token('btc').price; plot(if(p > 60000, p, null))
sum(series)
MEAN(series)
MEDIAN(series)
MAX(series)
min(series)
STDDEV(series)
Aggregation functions that reduce a series to a single value. // Mostly useful as inputs to other functions

5. Chart Annotations

Function Description Example
hline(value, [label]) Draws a horizontal line at a specific value. hline(5000000000, '5B TVL Level')
vline(timestamp, [label]) Draws a vertical line at a specific Unix timestamp. vline(1679529600, 'ARB Airdrop')

const [output, setOutput] = useState<any>(null)
const [parseErrors, setParseErrors] = useState<any[]>([])

useEffect(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hooks inside if could break react

@@ -491,7 +492,8 @@ export function ProDashboardAPIProvider({
deleteDashboard: deleteDashboardWithConfirmation,
saveDashboard,
saveDashboardName,
copyDashboard
copyDashboard,
setItems
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to create handler within context for new chart or reuse existing

@vrtnd
Copy link
Member

vrtnd commented Jun 20, 2025

image
need to do smth with scroll (also happens on single chart page), maybe make preview as drawer window which appears from right side of modal

@treeoflife2
Copy link
Member

@ReynardoEW

Here's some quick improvements i can think of based on my pinescript experience

  • Chained filtering (e.g., protocol('solana').category('lending').tvl)

    • Decide syntax now: dot-access vs extra arg (protocol(name, { category })).
    • Enforce that chaining never triggers extra HTTP calls until the final property is touched; otherwise you’ll multiply fetches.
    • Cache intermediate entities so the same filter chain isn’t recomputed in one script—or use lazy loading.
  • Built-in pctChange helper

    • Provide both simple and log versions (pctChange(series, 'log')).
    • Handle division-by-zero gracefully: return null or the last finite value, don’t emit Infinity.
  • Script version header (// version=1)

    • Parse it before anything else; if omitted, default to latest minor.
    • Keep a map { version → interpreter } so older dashboards stay reproducible.
  • Community script support

    • Allow public/private scripts.
  • Comment support

    • Single-line // and multi-line /* … */.
    • Strip comments during lexing to avoid column drift in error messages.
  • Server-side revision history

    • On each “Save”, persist { scriptId, version, code, timestamp }.
    • Limit stored revisions (e.g., keep the last 50) or enable pruning to control DB growth and prevent abuse.
  • Anchor plot / overlay flag

    • Add optional bool in plot() or main Indicator() (e.g., overlay=true).
    • UI: show anchor series on the left axis, others on the right axis to avoid scale collision.
  • Static typing of metric categories

    • Hard-code enum: Metric = 'tvl' | 'volume' | 'fees' | ….
    • During semantic analysis, ensure arithmetic only mixes compatible “unit kinds” (e.g., you can divide fees by tvl, but not tvl by price without explicit cast).
    • Provide cast(series, 'number') for edge cases.
  • Single-value labels

    • Expose label(name, value) that renders a small text element.
    • Cap total labels per chart (e.g., 10) and render them in an overlay layer, not as a series.
  • Max plots per chart (starter tier)

    • Hard limit in the parser (plotCount <= 5).
    • Return compile-time error if exceeded; easier than detecting at runtime.
    • We can always increase later when we get good response.
  • Show/hide/remove indicator

    • Each plot() returns an ID; store it in state.
    • Toggle visibility via checkbox; removal just deletes the ID from the state list, leaving the script untouched.
  • Size limits for indicators & version retention

    • Enforce: linesOfCode <= 300, scriptSizeKB <= 50.
    • Apply a rolling window on saved revisions (FIFO delete oldest when > N).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants