Follow your question further.
From a mortgage payment to a discovered equation. Explore every help category, see the syntax, and find a starting point.
Basics
Variables, operators, labels, flags, and sections -- the building blocks of every Cast model.
| FlagsControl how variables display and behave.
variable = value | flag
Control how variables display and behave. The | result flag promotes a variable to the Results section at the top of the output pane. If the variable uses if(), it shows as a Decision with a green/red pass/fail badge -- put the good outcome first in if() so the formatting is correct.
Keep full financial amounts with K, M, B or T
company_value = $3.2T | result
$3,200,000,000,000
Show in Results banner
profit = revenue - costs | result
Appears at top
Decision: use if() with | result to show a pass/fail badge
decision = if(irr > hurdle, "MEETS HURDLE", "BELOW HURDLE") | result
Shows green check when true, red X when false
Show in bar chart
revenue = price * quantity | chart
Visualized below
Hide from UI (use in formulas only)
temp = 100 | hidden
Invisible
Lock input (user can't edit)
pi = 3.14159 | locked
Read-only
Display as percentage
rate = 0.05 | percent
Shows as 5%
Waterfall chart (value creation bridge)
revenue = 500000 | bridge cogs = 200000 | bridge opex = 100000 | bridge ebitda = revenue - cogs - opex | bridge | result
Waterfall: Revenue -> -COGS -> -OpEx -> EBITDA
Add | chart to any variable — it gets its own bar chart. Add | result to bring it to the top.
| LabelsOverride the auto-generated label.
variable = value | Display Name
Override the auto-generated label. Snake_case names like "loan_term" already display as "Loan Term" automatically. Use labels when you want a short formula name or a label that differs from the variable name.
Short var name, pretty label
x = 100 | Purchase Price
UI shows "Purchase Price"
Auto-label (no | needed)
purchase_price = 100
UI shows "Purchase Price" automatically
# SectionsGroup related inputs together with section headers.
# SECTION NAME
Group related inputs together with section headers.
Add section header
# REVENUE price = 100 qty = 50
Groups inputs visually
Add divider line
---
Horizontal separator
> Visible TextAdd helper text that shows in the UI.
> Your message here
Add helper text that shows in the UI.
Guide users
> Enter your assumptions below
Shows in UI
// CommentsAdd comments that are hidden from the UI (code-only).
// Your note
Add comments that are hidden from the UI (code-only).
Internal notes
// TODO: Add validation
Not visible in UI
Finance
Calculate cash flows, investment returns, loan payments, and savings growth.
present_valueHow much a series of future payments is worth TODAY.
present_value(rate, periods, payment, [future], [type])
How much a series of future payments is worth TODAY.
Pay $1,000/year for 10 years at 5%. What's that worth today?
present_value = pv(0.05, 10, -1000)
$7,721.73
Same, but each payment lands at the START of the year
present_value = pv(0.05, 10, -1000, 0, "start")
$8,107.82 — a payment made sooner is worth more today
paymentHow much you pay each period on a loan.
payment(rate, periods, principal, [future], [type])
How much you pay each period on a loan.
$500k mortgage at 6% annual (0.5% monthly) for 30 years
monthly_payment = pmt(0.06/12, 360, 500000)
-$2,997.75
Same loan, but the payment is due at the START of each month
monthly_payment = pmt(0.06/12, 360, 500000, 0, "start")
-$2,982.84 — paying earlier costs slightly less per payment
Change 360 to 180 — see how a 15-year mortgage compares. Change 500000 to 750000 to scale up.
future_valueHow much your savings will grow to with regular contributions.
future_value(rate, periods, payment, [present], [type])
How much your savings will grow to with regular contributions.
Save $500/month for 10 years at 7% annual
retirement = fv(0.07/12, 120, -500)
$86,542.40
Same, but each deposit lands at the START of the month
retirement = fv(0.07/12, 120, -500, 0, "start")
$87,047.23 — depositing sooner earns an extra month of interest each time
net_present_valueWhat a series of future cash flows is worth today.
net_present_value(rate, cashflow1, cashflow2, ...)
What a series of future cash flows is worth today.
Project returns $1k, $1.5k, $2k over 3 years at 10% discount
project_value = npv(0.10, 1000, 1500, 2000)
$4,016.53
internal_rate_of_returnWhat percent return your investment generates.
internal_rate_of_return(cashflow1, cashflow2, ...)
What percent return your investment generates. First cash flow must be negative (your investment).
Invest $10k, get back $3k, $4k, $5k over 3 years
return_rate = irr(-10000, 3000, 4000, 5000)
8.9%
rateCalculate interest rate - what rate do you need to reach a goal with fixed payments?
rate(periods, payment, present_value, [future], [type])
Calculate interest rate - what rate do you need to reach a goal with fixed payments?
$500k loan with $2,998/month payment for 30 years - what's the rate?
monthly_rate = rate(360, -2998, 500000)
0.5% (6% annual)
Math
Basic math operations: conditionals, rounding, roots, logs, and aggregations like sum and avg.
ifConditional logic - choose different values based on a condition.
if(condition, true_value, false_value)
Conditional logic - choose different values based on a condition. Tip: if you flag an if() variable with | result, it shows as a Decision in the Results section with a pass/fail badge. Put the "good" outcome first and the "bad" outcome second so the green/red formatting is correct. If the answer IS the yes-or-no, skip if() and write the comparison on its own: beat_budget = revenue > budget shows "Yes" or "No".
Decision badge: shows green "MEETS HURDLE" or red "BELOW HURDLE"
decision = if(irr > hurdle_rate, "MEETS HURDLE", "BELOW HURDLE") | Decision | result
MEETS HURDLE (green badge in Results section)
Change irr > hurdle_rate to irr > 0.20 — raise the bar and watch the badge flip red.
caseMulti-branch conditional - check multiple conditions and return the first match.
case(cond1, val1, cond2, val2, ..., else_value)
Multi-branch conditional - check multiple conditions and return the first match. List the best outcome first and worst last; the Results banner auto-colors green-to-red by branch order. Also supports a readable multiline form. Also: switch()
Multiline form (preferred for readability)
outcome = case when moic > 2 => "Strong Return" when moic > 1.2 => "Moderate Return" else => "Underperform" | result
Strong Return (green), Moderate Return (amber), or Underperform (red)
Single-line form
outcome = case(moic > 2, "Strong Return", moic > 1.2, "Moderate Return", "Underperform") | result
Same behavior as multiline form
Change moic > 2 to moic > 3 — the first branch gets harder to hit. Try adding a fourth when clause.
sumAdd multiple values together.
sum(value1, value2, ...)
Add multiple values together.
Total closing costs
total = sum(5000, 2000, 1500)
8500
prodMultiply all values together.
prod(value1, value2, ...)
Multiply all values together. Works with individual numbers or arrays.
Compound growth over 3 years
total_growth = prod(1.05, 1.08, 1.03)
1.1687
countCount how many values (length of a list or array).
count(value1, value2, ...)
Count how many values (length of a list or array). NOTE: this counts EVERYTHING, including blanks and text -- unlike Excel's own COUNT, which only counts numbers. That difference is why count() always exports to Excel as a plain number, never a live formula.
Count data points
n = count(10, 20, 30, 40)
4
sumproductMultiply corresponding elements of two arrays and sum the results (dot product).
sumproduct(array1, array2)
Multiply corresponding elements of two arrays and sum the results (dot product). Like SUMPRODUCT in Excel.
Weighted score calculation
sumproduct([0.3, 0.5, 0.2], [90, 80, 70])
81
avgCalculate average (mean) of values.
avg(value1, value2, ...)
Calculate average (mean) of values.
Average monthly revenue
avg_monthly = avg(10000, 12000, 11000)
11000
Try avg(10000, 12000, 11000, 50000) — one outlier pulls the mean up. Compare with median().
minFind minimum (smallest) value.
min(value1, value2, ...)
Find minimum (smallest) value.
Lowest price from options
lowest = min(100, 85, 120)
85
maxFind maximum (largest) value.
max(value1, value2, ...)
Find maximum (largest) value.
Highest monthly sales
peak = max(10000, 15000, 12000)
15000
absAbsolute value - remove negative sign.
abs(value)
Absolute value - remove negative sign.
Distance regardless of direction
distance = abs(-50)
50
roundRound to specified decimal places.
round(value, decimals)
Round to specified decimal places.
Round to 2 decimal places
price = round(123.456, 2)
123.46
Change 2 to 0 for whole dollars. Change to -2 to round to the nearest hundred.
floorRound down to nearest integer.
floor(value)
Round down to nearest integer.
Number of complete years
years = floor(4.7)
4
ceilRound up to nearest integer.
ceil(value)
Round up to nearest integer.
Boxes needed (always round up)
boxes = ceil(4.2)
5
sqrtSquare root.
sqrt(value)
Square root.
Standard deviation calculation
std = sqrt(variance)
(depends on variance)
powRaise to power.
base^exponent
Raise to power. Use ^ notation — pow() is accepted but ^ is canonical.
Compound growth
future = 100 * 1.05^10
162.89
Change 1.05^10 to 1.05^30 — see compounding accelerate over time. Change 1.05 to 1.10 for 10% growth.
sinSine of an angle.
sin(angle)
Sine of an angle. Works in RADIANS by default; add "deg" for degrees. Also: cos, tan, asin, acos, atan.
Degrees (just add "deg")
height = sin(30 deg)
0.5
Radians (Cast knows pi)
height = sin(pi / 6)
0.5
Don't name a variable "deg" or "rad" — those are unit names and will collide. Use angle_deg.
cosCosine of an angle.
cos(angle)
Cosine of an angle. Radians by default; add "deg" for degrees.
Degrees
x = cos(60 deg)
0.5
tanTangent of an angle.
tan(angle)
Tangent of an angle. Radians by default; add "deg" for degrees. Inverses: asin, acos, atan (return radians).
Degrees
slope = tan(45 deg)
1
lnNatural logarithm (base e).
ln(value)
Natural logarithm (base e).
Inverse of exponential growth
rate = ln(2)
0.693 (ln doubling)
A whole data column at once: one log per row
log_compute = ln(data.compute) trend = pearson(data.year, log_compute)
A straight-line trend on the log scale means exponential growth
log10Logarithm base 10.
log10(value)
Logarithm base 10.
Orders of magnitude
magnitude = log10(1000)
3
expe raised to power (e^value).
exp(value)
e raised to power (e^value).
Exponential growth
growth = exp(0.05)
1.051
concatJoin text and numbers into a string.
concat(value1, value2, ...)
Join text and numbers into a string.
Build message with formatting
msg = concat("Total: $", round(amount, 2))Total: $1234.56
Calculus
Derivatives and integrals -- as plain English. Get the slope of a formula, the area under a curve, or the rate of change of your data.
derivativeThe rate of change of a formula, written as a new formula (its slope).
derivative of FORMULA with respect to VARIABLE
The rate of change of a formula, written as a new formula (its slope). Handles full and partial derivatives -- with several variables, the others are held constant. Say "partial derivative of ..." if you prefer. Also: derivative("x^2", "x").
Slope formula of a parabola
slope = derivative of x^2 + 3*x with respect to x
2 * x + 3
Partial derivative (y held constant)
dz = partial derivative of x^2 * y with respect to x
2 * x * y
Change x^2 to x^3 — derivative becomes 3*x^2. Change 3*x to 5*x to see the constant carry through.
slopeThe slope of a formula at one specific point -- a single number (the instantaneous rate of change).
slope of FORMULA at VARIABLE = VALUE
The slope of a formula at one specific point -- a single number (the instantaneous rate of change). Also: slope_at("x^2", "x", 4).
Slope of a parabola at x = 4
rise = slope of x^2 + 3*x at x = 4
11
Change 4 to 0 — slope at the bottom of the parabola is 3. Try a negative value to see the downward side.
integralThe area under a formula between two points (a definite integral).
integral of FORMULA from A to B
The area under a formula between two points (a definite integral). Add "with respect to VARIABLE" when the formula has more than one variable. Also: integral("x^2", 0, 3).
Area under a parabola from 0 to 3
area = integral of x^2 from 0 to 3
9
Distance from a speed formula (speed = 9.8*t)
distance = integral of 9.8 * t from 0 to 5
122.5
Change 3 to 6 — area roughly doubles. Change x^2 to x to see a triangle instead of a curve.
rate_of_changeThe slope between each point of a data series -- a numeric (discrete) derivative.
rate_of_change(series) or rate_of_change(values, x_values)
The slope between each point of a data series -- a numeric (discrete) derivative. Velocity from position readings, marginal cost from a cost curve. Pass a second series for the x-axis spacing. Also: gradient(series).
Velocity from position readings
position = [0, 1, 4, 9, 16] velocity = rate_of_change(position)
[1, 2, 4, 6, 7]
With explicit time spacing
velocity = rate_of_change(position, time)
slope using real time gaps
Replace [0,1,4,9,16] with [0,1,2,3,4] — velocity becomes constant. Try [0,2,8,18] for acceleration.
area_underThe total area under a data series using the trapezoidal rule -- a numeric integral of measurements.
area_under(series) or area_under(values, x_values)
The total area under a data series using the trapezoidal rule -- a numeric integral of measurements. Total distance from velocity, total energy from power. Pass a second series for the x-axis spacing. Also: trapezoid(series).
Total flow from per-step readings
flow = [0, 2, 4, 6] total = area_under(flow)
9
With explicit time spacing
distance = area_under(velocity, time)
area using real time gaps
Replace [0,2,4,6] with [0,0,10,0] — a spike. Watch total area react to the sudden peak.
Summary Stats
Descriptive statistics for understanding your data: averages, spread, percentiles, and confidence intervals.
medianMiddle value when data is sorted.
median(value1, value2, ...)
Middle value when data is sorted. More robust than mean for skewed data.
Median of 5 values
mid = median(10, 20, 30, 40, 50)
30
stdevSample standard deviation - measures how spread out values are from the mean.
stdev(value1, value2, ...)
Sample standard deviation - measures how spread out values are from the mean.
Spread of test scores
spread = stdev(85, 90, 78, 92, 88)
5.36
Replace the numbers with 89, 90, 91 — spread shrinks to near zero. Try 10, 50, 90 for high spread.
spreadHow spread out are values relative to their average? Returns a percentage.
spread(value1, value2, ...)
How spread out are values relative to their average? Returns a percentage.
Price volatility
volatility = spread(100, 110, 90, 105, 95)
8% variation
standard_errorHow precisely do you know the average? Smaller = more reliable.
standard_error(value1, value2, ...)
How precisely do you know the average? Smaller = more reliable.
Precision of mean estimate
precision = standard_error(98, 100, 102, 99, 101)
0.71
confidence95% confidence interval for the mean.
confidence(value1, value2, ...)
95% confidence interval for the mean. Returns [lower, upper] bounds.
Confidence interval for measurements
bounds = confidence(98, 100, 102, 99, 101)
[98.04, 101.96]
percentileFind the value at a given percentile.
percentile(p, value1, value2, ...)
Find the value at a given percentile. percentile(90, ...) = value below which 90% of data falls.
90th percentile salary
top10 = percentile(90, 50000, 60000, 70000, 80000, 120000)
104,000
deviation_fromHow far is a value from the average, in standard deviations? Above 2 or below -2 is unusual.
deviation_from(value, mean, stdev)
How far is a value from the average, in standard deviations? Above 2 or below -2 is unusual.
Is 110 unusual when mean=100, stdev=10?
z = deviation_from(110, 100, 10)
1.0 (one std dev above — not unusual)
varianceSample variance — the square of standard deviation.
variance(value1, value2, ...)
Sample variance — the square of standard deviation.
Variance of returns
var = variance(5, 10, 15, 20, 25)
62.5
Sig Tests
Compare groups and test relationships. Read the p-value, effect size, and assumptions together.
ttestTwo-sample t-test (Welch) - tests whether two groups have different averages.
ttest(group1, group2)
Two-sample t-test (Welch) - tests whether two groups have different averages. No assumption of equal variance.
Compare drug vs placebo response
ttest([5, 7, 8, 6, 9], [2, 3, 4, 3, 5])
{ p_value: 0.003, significant: true }
Change the second group to [5,6,7,6,8] — groups converge and p_value rises above 0.05 (no longer significant).
paired_ttestPaired t-test - tests whether before/after measurements differ on the SAME subjects.
paired_ttest(before, after)
Paired t-test - tests whether before/after measurements differ on the SAME subjects.
Did blood pressure drop after medication?
paired_ttest([120, 130, 125], [110, 118, 112])
{ p_value: 0.02, significant: true }
Change the after values closer to before — watch mean_diff shrink toward zero and p_value rise.
anova_testOne-way ANOVA - tests whether 3+ groups have different averages.
anova_test(group1, group2, group3, ...)
One-way ANOVA - tests whether 3+ groups have different averages. Like a t-test but for multiple groups.
Compare 3 fertilizers on plant growth
anova_test([4,5,6], [7,8,9], [1,2,3])
{ f_statistic: 27.0, significant: true }
Add a fourth group [10,11,12] — watch the F-statistic respond to the wider spread between groups.
pearsonPearson correlation - measures linear relationship between two variables.
pearson(x_values, y_values)
Pearson correlation - measures linear relationship between two variables. r = 1 (perfect positive), r = -1 (perfect negative), r = 0 (no relationship).
Correlation between study hours and test scores
pearson([1,2,3,4,5], [50,55,65,70,80])
{ r: 0.98, p_value: 0.003 }
Add a fourth group [10,11,12] — watch the F-statistic respond to the wider spread between groups.
chisquareChi-square goodness of fit - tests whether observed frequencies match expected frequencies.
chisquare(observed, expected)
Chi-square goodness of fit - tests whether observed frequencies match expected frequencies.
Is a die fair? (expected: equal frequencies)
chisquare([18,16,14,12,17,13], [15,15,15,15,15,15])
{ chi2: 2.0, significant: false }
mann_whitneyMann-Whitney U test - non-parametric alternative to the t-test.
mann_whitney(group1, group2)
Mann-Whitney U test - non-parametric alternative to the t-test. Use when your data is not normally distributed or has outliers.
Compare satisfaction ratings (ordinal data)
mann_whitney([3,4,5,4,5], [1,2,3,2,2])
{ u_statistic: 22.5, p_value: 0.01, significant: true }
Change the after values closer to before — watch mean_diff shrink toward zero and p_value rise.
wilcoxonWilcoxon signed-rank test - non-parametric alternative to the paired t-test.
wilcoxon(before, after)
Wilcoxon signed-rank test - non-parametric alternative to the paired t-test. Use for before/after comparisons when data is not normally distributed.
Did pain scores improve after treatment?
wilcoxon([7,8,6,9,7], [4,5,3,6,4])
{ w_statistic: 0, p_value: 0.04, significant: true }
shapiro_wilkShapiro-Wilk normality test - checks if your data follows a bell curve (normal distribution).
shapiro_wilk(data)
Shapiro-Wilk normality test - checks if your data follows a bell curve (normal distribution). Run this before choosing between parametric and non-parametric tests.
Is my data normally distributed?
shapiro_wilk([2.3, 2.5, 2.7, 2.9, 3.1])
{ w_statistic: 0.99, significant: false } (looks normal)
Time Series
Analyze a series over time: smooth it, measure period-over-period change and growth, find repeating cycles, and reveal the strongest frequency with an FFT.
moving_averageSmooths a series by averaging each point with the ones before it.
moving_average(series, window)
Smooths a series by averaging each point with the ones before it. The first window-1 points are blank (not enough history yet).
3-point moving average
smoothed = moving_average(sales, 3)
NaN, NaN, 20, 30, 40
Change window from 3 to 7 — more smoothing, slower to react. Try 2 for almost no smoothing.
cumulativeRunning total: each point is the sum of everything up to and including it.
cumulative(series)
Running total: each point is the sum of everything up to and including it.
Cumulative sales
total = cumulative(sales)
10, 30, 60, 100
Try cumulative(differences(sales)) — you get the original series back. Running total of changes = original.
differencesPeriod-over-period change: this point minus the previous one.
differences(series)
Period-over-period change: this point minus the previous one. First value is blank.
Change in sales
change = differences(sales)
NaN, 10, 10, 10
Try avg(differences(sales)) — the average period change. A perfectly flat series gives all zeros.
growth_ratePercent change period over period, as a fraction (0.1 means +10%).
growth_rate(series)
Percent change period over period, as a fraction (0.1 means +10%).
Sales growth
growth = growth_rate(sales)
NaN, 1, 0.5, 0.33
Replace sales with a flat series like [100,100,100] — all zeros. Try doubling each step to see 100% growth.
lagShifts the series forward by N periods, filling the front with blanks.
lag(series, periods)
Shifts the series forward by N periods, filling the front with blanks. Use it to compare each point to one N steps earlier.
Last year same month
last_year = lag(sales, 12)
shifted 12 periods
Change 12 to 1 — previous month. Subtract lag(sales,1) from sales to get period-over-period change.
autocorrelationHow strongly the series resembles itself N steps later.
autocorrelation(series, lag)
How strongly the series resembles itself N steps later. Near 1 means a strong repeating pattern; near 0 means no relationship.
Does it echo itself?
repeat = autocorrelation(sales, 1)
0.95 (strong)
Try autocorrelation on a repeating series — near 1 means strong pattern. Try lag 12 on monthly sales.
detrendRemoves the best-fit straight line, leaving the wiggles around the trend.
detrend(series)
Removes the best-fit straight line, leaving the wiggles around the trend. A perfectly straight series detrends to zeros.
Strip the trend
wiggle = detrend(sales)
ups and downs only
Try on a straight line like [1,2,3,4,5] — residuals become all zeros. A curved series shows the bend.
frequency_spectrumThe strength of each cycle in the data (an FFT).
frequency_spectrum(series)
The strength of each cycle in the data (an FFT). A tall bar means a strong repeating cycle at that frequency. Plot it to SEE which cycles dominate.
See the cycles
spectrum = frequency_spectrum(signal)
plot it as a line
dominant_cycleThe length (in samples) of the strongest repeating cycle, found from the frequency spectrum.
dominant_cycle(series)
The length (in samples) of the strongest repeating cycle, found from the frequency spectrum. e.g. monthly sales with a yearly cycle returns ~12.
Find the cycle
period = dominant_cycle(sales)
12
Explore Data
Get to know your data before analyzing it. Summary stats, missing value checks, distributions, and scatter plots.
describeSummary statistics in one line.
describe(column) or describe(data)
Summary statistics in one line. On a single column: count, mean, stdev, min, max, median, quartiles, missing. On the whole dataset: summary for every column.
Describe one column
stats = describe(data.yield)
{count: 100, mean: 42.5, stdev: 12.3, min: 10, max: 95, ...}
Describe entire dataset
overview = describe(data)
{temp: {count: 100, mean: 250, ...}, yield: {count: 100, mean: 42, ...}}
Try describe(data) on the whole dataset — you get a summary for every column at once.
missingCount how many values are blank or NaN in a column.
missing(column)
Count how many values are blank or NaN in a column. Run this before analysis to know what you are working with.
Check for missing scores
blanks = missing(data.score)
3 (3 rows have no score)
Run this before any analysis — a column with 20 missing values will skew every stat. Clean first.
count_uniqueCount distinct values in a column.
count_unique(column)
Count distinct values in a column. Low count (2-10) means categorical, high count means continuous. Helps you decide which analysis to use. NOTE: Excel has its own UNIQUE() that returns the LIST of distinct values, not a count -- Cast's "unique" alias still works, but count_unique is the honest name for what this actually returns.
Is status categorical?
n_status = count_unique(data.status)
3 (e.g., active/inactive/pending -- categorical)
Is revenue continuous?
n_rev = count_unique(data.revenue)
487 (many distinct values -- continuous)
Charts
Visualize your data with one line. Load data in the DATA tab, then use plot to create scatter, bar, line, area, or horizontal bar charts.
confusion_matrixCounts classifications and automatically draws the chart in one line.
confusion_matrix(actual, predicted)
Counts classifications and automatically draws the chart in one line. No extra chart flag or plot line. Supply matching nonempty lists of class labels. Display rows are predicted classes; columns are actual classes. Each cell shows its count; the summary shows accuracy and sample count. Up to 50 classes; remove missing labels first.
Compare known answers with predictions
quality = confusion_matrix(["Pass","Fail"],["Pass","Pass"])
One correct classification and one mistake; 50% accuracy
Scientific PlotsAn editable showcase of scatter, line, bar, area, histogram, box, violin, heatmap, contour, 3D scatter, a connected 3D path, surfaces and a confusion matrix.
Templates → Math, Science & Engineering → Scientific Plots
An editable showcase of scatter, line, bar, area, histogram, box, violin, heatmap, contour, 3D scatter, a connected 3D path, surfaces and a confusion matrix. Sample data is illustrative. Hover for values, drag 3D plots to rotate and use Play/Pause or the time scrubber for moving surfaces. The camera button saves an image.
Surface plots over time
motion = moving_surface("sin(x)*cos(y)*cos(t)",8,2)A changing surface with fixed height and color ranges
box_plotCompare spread, median and outliers with a box plot.
box_plot(values)
Compare spread, median and outliers with a box plot.
Try this editable example
spread_plot = box_plot([2,4,5,6,9])
An interactive scientific plot in Charts
violin_plotInspect the shape of a distribution with a violin plot and a box summary.
violin_plot(values)
Inspect the shape of a distribution with a violin plot and a box summary.
Try this editable example
density_plot = violin_plot([2,4,5,6,9])
An interactive scientific plot in Charts
heatmapColor a rectangular grid of finite numbers.
heatmap(grid)
Color a rectangular grid of finite numbers. Columns are X; rows are Y.
Try this editable example
map = heatmap([[1,2],[3,4]])
An interactive scientific plot in Charts
contour_plotShow equal-height contours in a rectangular numeric grid.
contour_plot(grid)
Show equal-height contours in a rectangular numeric grid.
Try this editable example
levels = contour_plot([[1,2],[3,4]])
An interactive scientific plot in Charts
scatter_3dInspect three measurements for each point.
scatter_3d(x,y,z)
Inspect three measurements for each point. Named lists label the axes. Hover for point details. Supply matching lists of finite numbers. Drag to rotate; use the camera button to download a PNG.
Try this editable example
points = scatter_3d([1,2],[3,4],[5,6])
An interactive scientific plot in Charts
path_3dDraw 3D paths by connecting at least two samples in their original order.
path_3d(x,y,z)
Draw 3D paths by connecting at least two samples in their original order. Supply matching lists of finite coordinates. Drag to rotate; hover for the sample number. The path draws automatically.
Try this editable example
trajectory = path_3d([0,1,0],[0,0,1],[0,1,2])
An interactive scientific plot in Charts
surface_plotDraw a surface from a formula using x and y.
surface_plot("formula", extent)Draw a surface from a formula using x and y. Both axes run from minus extent to plus extent; default extent is pi. Drag to rotate.
Try this editable example
height = surface_plot("sin(x)*cos(y)",2)An interactive scientific plot in Charts
moving_surfacePlay a surface formula using x, y and t.
moving_surface("formula", seconds, extent)Play a surface formula using x, y and t. Defaults: 8 seconds and extent pi. A fixed 31 × 31 grid and 33 time samples preserve the height and color scales. Supports pi/e, arithmetic and sin/cos/tan/exp/log/sqrt/abs/sinh/cosh/tanh. Other symbols, assignments and nonfinite heights are rejected. This displays sampled formulas; it does not solve differential equations.
Try this editable example
motion = moving_surface("sin(x)*cos(y)*cos(t)",8,2)An interactive scientific plot in Charts
| chartTwo or more scalar values share one X axis.
value = expression | chart
Two or more scalar values share one X axis. Active and each saved scenario get their own chart with the same item order. Each bar keeps its own unit label; compare heights only for matching units. Single values compare scenarios. Flagged data series and forecasts with matching time and value units share one line chart by default. Each line has a different color and ends with its name and final value; narrow or crowded charts use a legend. Money labels show whole amounts, while calculations keep their cents.
Compare revenue and costs
revenue = $1000 | chart costs = $400 | chart
Revenue and Costs on one X axis; save a scenario to add its chart.
chartVisualize grid columns or computed lists.
chart columns [vs column] [| type]
Visualize grid columns or computed lists. Pipes choose the type: chart yield | box. Add a title after a pipe with single or double quotes: chart revenue | line | "12 Month Forecast". Forecast charts inherit month/year or named calendar labels. Use "vs" for an explicit X axis. Comma-separate multiple Y columns. A separate chart line groups selected series; | chart keeps default output grouping. Distributions take one list without vs and keep numeric bins. Default: scatter for "vs", bar otherwise. Also: plot, graph (same thing), and existing as aliases.
Scatter plot -- paste this sample data in the DATA tab to try it: temperature | yield -- 60 | 45 -- 70 | 52 -- 75 | 58 -- 80 | 63 -- 85 | 70 -- 90 | 74
chart yield vs temperature
Scatter chart showing temperature on X axis, yield on Y axis with trend visible
Bar chart -- sample data: region | revenue -- North | 175000 -- South | 145000 -- East | 120000 -- West | 85000
chart revenue | bar
Vertical bars, one per row, colored with the selected theme
Multi-series line -- sample data: quarter | revenue | profit -- Q1 | 100000 | 30000 -- Q2 | 120000 | 45000 -- Q3 | 160000 | 80000 -- Q4 | 200000 | 110000
chart revenue, profit vs quarter | line
Two smooth lines in different theme colors, labeled with their names and final values
Horizontal bar (good for long category names)
chart sales | hbar
Horizontal bars ranked largest on top, values labeled to the right
Area chart (line with fill)
chart revenue vs quarter | area
Smooth curve with subtle fill underneath
Distribution of one list
chart yield | box
Box plot of the list; also use | histogram or | violin
Change | bar to | line — same data, different shape. Add a second column: chart revenue, profit | bar for multi-series.
| animate (Animated Line Chart)Add | animate (alias | play) to a line chart to get a time-playback animation.
chart col [, col] as line | animate
Add | animate (alias | play) to a line chart to get a time-playback animation. A Play/Pause button and a time scrubber reveal the curve point by point, with the y-axis pinned. Great for watching a forecast or ODE solution unfold over time.
Watch a forecast unfold
initial = 100 growth = 10% forecast 10 years: balance starts at initial, then previous balance * (1 + growth) chart balance as line | animate
Animated chart: press Play to watch the curve build year by year
Two coupled series animated together
chart revenue, profit as line | animate
Both curves animate together, y-axis stays fixed
Press Play and drag the scrubber to jump to any point. Add a second series (chart a, b as line | animate) to watch both unfold.
scatterScatter plot of two data columns.
scatter(x_column, y_column)
Scatter plot of two data columns. Prefer "chart Y vs X" for grid data -- scatter() is for computed arrays.
Using data proxy columns
my_scatter = scatter(data.temp, data.yield)
Scatter plot showing each data point
histogramDistribution chart for one column.
histogram(column) or histogram(column, bins)
Distribution chart for one column. Each outlined bar has its count above it; the horizontal axis labels the measured amounts. See if data is normal, skewed, or bimodal. Auto-picks bin count or specify your own. Use the camera button to download a PNG.
Distribution of yields
dist = histogram(data.yield)
Histogram with auto-calculated bins
Fine-grained distribution
dist = histogram(data.yield, 20)
Histogram with 20 bins
Data Cleaning
Fix messy data before analysis. Remove blanks, fill in missing values, and strip outliers that skew your results.
drop_missingRemove all blank, null, and NaN values from an array.
drop_missing(column)
Remove all blank, null, and NaN values from an array. Use this to clean a column before running statistics on it.
Clean a column before averaging
clean_scores = drop_missing(data.score) avg_score = avg(clean_scores)
Average of non-blank scores
Wrap any stat in drop_missing() first — avg(drop_missing(data.score)) ignores the blanks.
fill_missingReplace blank/NaN values with a number or strategy.
fill_missing(column, replacement)
Replace blank/NaN values with a number or strategy. Replacement can be a number, "mean", "median", or "zero".
Fill blanks with the column average
patched = fill_missing(data.score, "mean") result = stdev(patched)
Stdev with blanks filled by mean
Fill blanks with zero
clean = fill_missing(data.revenue, 0)
Revenue with blanks as 0
Try fill_missing(data.score, 'mean') then stdev — the distribution barely changes vs. dropping rows.
remove_outliersDrop extreme values using the IQR method.
remove_outliers(column) or remove_outliers(column, threshold)
Drop extreme values using the IQR method. Values below Q1 - 1.5*IQR or above Q3 + 1.5*IQR are removed. Adjust the threshold (default 1.5) to be more or less aggressive.
Remove extreme prices before analysis
clean_prices = remove_outliers(data.price) avg_price = avg(clean_prices)
Average without extreme values
Strict outlier removal (threshold = 1.0)
tight = remove_outliers(data.price, 1)
Only values close to the middle 50%
Filtering
Slice your data by conditions. Keep rows that match, compare groups, or find the strongest predictors.
whereFilter an array to keep only elements where a condition is true.
values where condition > threshold
Filter an array to keep only elements where a condition is true. Works with data columns or any array. Like SQL WHERE but for arrays.
Yields where temperature exceeds 300
hot_yields = data.yield where data.temp > 300
[40, 50] (only rows where temp > 300)
Average of a filtered group
avg_hot = avg(data.yield where data.temp > 300)
45 (average of filtered values)
Compare two groups with a t-test
result = ttest( data.yield where data.group == "A", data.yield where data.group == "B" )
{ p_value: 0.03, significant: true }
Change > 300 to > 200 — more rows qualify, mean shifts. Swap > for < to get the other group.
subsetFilter all columns at once and save as a new dataset.
subset(data, column > threshold)
Filter all columns at once and save as a new dataset. All columns keep the same rows. Use the result like data.* (e.g., hot.yield, hot.pressure), or filter that dataset again using its own columns.
Filter to high-temperature rows, then use all columns
hot = subset(data, data.temp > 300) avg_yield = avg(hot.yield) avg_pressure = avg(hot.pressure)
All columns filtered to rows where temp > 300
Split into groups and compare with t-test
group_a = subset(data, data.group == 1) group_b = subset(data, data.group == 2) result = ttest(group_a.score, group_b.score)
{ p_value: 0.03, significant: true }
Combine conditions with and / or / not
clean = subset(data, data.temp < 110 and data.yield < 60) result = pearson(clean.temp, clean.yield)
Only rows that pass both checks, every column still lined up
Change the threshold to split the data differently — the filtered columns all move together.
top_predictorsScreen all numeric columns in your data against a target and rank them by correlation strength.
top_predictors(target_column)
Screen all numeric columns in your data against a target and rank them by correlation strength. Saves you from writing pearson() for every column manually.
Find what drives yield
drivers = top_predictors(data.yield)
[{column: "temp", r: 0.98}, {column: "catalyst", r: 0.95}, ...]
Find what predicts churn
churn_factors = top_predictors(data.churn)
[{column: "tenure", r: -0.82}, {column: "tickets", r: 0.71}, ...]
Change data.yield to any other numeric column — the ranking reshuffles to show its drivers.
Forecasts
Project values forward over time. Debt paydowns, compound growth, and multi-year financial models.
timed valueApply an amount once, at a chosen forecast period; it is zero in other periods.
event = amount in year period
Apply an amount once, at a chosen forecast period; it is zero in other periods. Use the event row in an ordinary formula. Random timing: event = amount in any year from 1 to 18. Works with year, month, week, day or period. Timing must match the forecast and lie within its horizon. Also: at_period(amount, period), inside a forecast. Monte Carlo can override timing with event occurs in year 1 to 18, choosing one whole year once per simulation.
A crash and a later withdrawal
forecast 3 years: crash = -30% in year 1 withdrawal = $100 in year 2 balance starts at $1000, then previous balance * (1 + crash) - withdrawal ending = final(balance) | result
Crash in year 1 only; withdrawal in year 2 only; ending balance $600
forecastCreate N calculated periods, starting at Month 1 or Year 1 after the first calculation.
forecast N years: Value starts at base, then previous Value * 1.05
Create N calculated periods, starting at Month 1 or Year 1 after the first calculation. Tables and categorical charts inherit period names. For named months: forecast 12 months starting September 2026: (year optional). Add | include start after the colon to show Month 0 or Year 0. This starting point is display-only, excluded from totals, averages and financial functions. Missing starting values show a gap. A blank line ends the block. N can be literal or variable.
Named months with a separate starting point
forecast 12 months starting September 2026: | include start revenue starts at $100, then previous revenue * 1.1 chart revenue | line | "12 Month Forecast"
Month 0 (August 2026): $100. September 2026: $110. Totals include only the twelve calculated months.
Literal number (blank line ends block)
forecast 5 years: Rev starts at 100000, then previous Rev * 1.1 Profit = Rev * 0.2 // Alternative format (same calculation): // Rev = 100000, then previous Rev * 1.1
Rev for year_1...year_5, Profit for year_1...year_5
Monthly forecast
forecast 12 months: MRR = starting_mrr * 1.03^month
MRR for month_1...month_12
Variable for flexibility
hold = 7 forecast hold years: Value starts at 1000, then previous Value * growth
Projects for 7 years (dynamic)
With line chart visualization
base = $1M forecast 5 years: revenue = base * 1.05^year | chart
Line chart showing revenue trend Year 1..5
Change 5 to 10 — more periods appear. Change 1.1 to 1.2 to watch compounding accelerate.
previousRead the prior period value.
Value starts at initial_value, then previous Value * 1.05
Read the prior period value. The starting value is before period 1; the formula runs in every period. Also: Value = initial_value, then previous Value * 1.05 (same thing). Existing trailing ", starts at" syntax still works.
Debt paydown
Balance starts at 100000, then previous Balance - 10000
Before year 1: 100k. Year 1: 90k. Year 2: 80k.
finalGet the last value from a forecast series or list.
final(SeriesVariable)
Get the last value from a forecast series or list. Also: terminal() (same thing).
Final portfolio value
exit_value = final(Portfolio)
Value from final year
period accessorAccess a specific period from a forecast series.
year_N.Variable, month_N.Variable, week_N.Variable, or day_N.Variable
Access a specific period from a forecast series. The accessor prefix matches the forecast unit.
Get year 3 balance
y3 = year_3.Balance
Balance from 3rd year only
Get month 6 revenue
m6 = month_6.Revenue
Revenue from 6th month only
Constraints
Declare named pass/fail rules -- covenants, policy thresholds, engineering limits. Each renders as a green / red / amber chip in the results.
constraintsList named conditions that each render as a pass/fail card in the results.
constraints: expression expression | Label
List named conditions that each render as a pass/fail card in the results. A blank line or the next variable assignment ends the block. No | result flag and no boolean variable needed.
Unlabeled -- shows the expression
revenue = 500000 costs = 420000 constraints: revenue > costs
Green chip: "revenue > costs"
Labeled covenant checks
debt_ratio = 0.31 constraints: debt_ratio < 0.40 | Debt ceiling debt_ratio < 0.50 | Leverage OK
Two green chips: Debt ceiling, Leverage OK
Synonyms all work (covenants / rules / policies / requirements)
debt_to_ebitda = 6.1 covenants: debt_to_ebitda <= 5.5 | Leverage ceiling
Red chip: Leverage ceiling (breaches, 6.1 > 5.5)
Change debt_ratio from 0.31 to 0.45 — the chip flips red. Add a third constraint on the next line.
Model Backsolve
Describe a deal as known inputs (given) plus limits (require:), and Cast assembles the whole model from a trusted relationship library -- no AI in the math. Leave one input unknown (assume) and the Boundary Solver finds the value where a covenant hits its limit.
givenDeclare the known inputs of a deal -- its fixed numbers.
given: name = value name = value
Declare the known inputs of a deal -- its fixed numbers. One input per line inside the given: block. Cast derives everything else from the trusted relationship library (the physics). Pair with require: covenants and Cast assembles + checks the whole model. After a run, the "Full Model" panel in the results lets you type a new value over any of these inputs and press Cast again -- everything re-derives and the covenants re-judge, without editing your code.
An LBO deal that passes its leverage covenant
given: entry_ebitda = 50 revenue_cagr = 0.10 entry_multiple = 10 debt_pct = 0.60 interest_rate = 0.08 amort_pct = 0.05 require: leverage_ratio < 6 | Max leverage
Covenant report: Max leverage PASS (5.45 < 6)
Change debt_pct from 0.60 to 0.75 — watch the covenant flip from PASS to FAIL.
assumeLeave exactly one input UNKNOWN.
assume knob
Leave exactly one input UNKNOWN. Pair it with a covenant that bounds a variable depending on it, and the Boundary Solver finds the knob value where the covenant sits at its limit -- "how much can this deal carry?". No arbitrary guess: it searches for the answer.
How much debt can the deal carry?
given: entry_ebitda = 50 revenue_cagr = 0.10 entry_multiple = 10 interest_rate = 0.08 amort_pct = 0.05 assume debt_pct require: leverage_ratio < 6 | Max leverage
Boundary Solver: debt_pct = 66%
Change the covenant limit from < 6 to < 5 — the solver finds a tighter debt ceiling.
backsolveName the variables you want Cast to derive when they are not already implied by a covenant.
backsolve target, target
Name the variables you want Cast to derive when they are not already implied by a covenant. Comma-separated. Cast back-chains each target through the relationship library and shows the result.
Derive the leverage metrics directly
given: entry_ebitda = 50 revenue_cagr = 0.10 entry_multiple = 10 debt_pct = 0.60 interest_rate = 0.08 amort_pct = 0.05 backsolve leverage_ratio, dscr
leverage_ratio = 5.45, dscr = 1.41
relationshipsDefine or override the physics Cast derives from.
relationships: target = expression
Define or override the physics Cast derives from. Built-ins (the trusted LBO library) are used automatically; add or replace formulas here. Synonyms: physics:, formulas:, automated calculations:. On Cast, a backsolve model auto-reveals the library into an "automated calculations:" block so you can see and edit every formula.
Add your own relationship, then backsolve from it
given: price = 100 qty = 12 relationships: revenue = price * qty backsolve revenue
revenue = 1200
Domain Libraries
Browse the built-in formula libraries Cast uses to assemble models. Each domain lists its trusted relationships -- the exact formulas behind Model Backsolve, with no AI in the math.
Leveraged Buyout 16 formulas
ebitda = entry_ebitda * (1 + revenue_cagr)
earnings after one year of growth
purchase_price = entry_ebitda * entry_multiple
what you pay: entry earnings times the multiple
debt = purchase_price * debt_pct
borrowed portion of the purchase price
equity = purchase_price - debt
your own cash in the deal
interest = debt * interest_rate
annual interest owed on the debt
amortization = debt * amort_pct
mandatory principal repaid each year
debt_service = interest + amortization
total debt payment due each year
leverage_ratio = debt / ebitda
turns of debt the company carries
interest_coverage = ebitda / interest
how easily earnings cover interest
dscr = ebitda / debt_service
earnings vs. all debt payments due
exit_ebitda = entry_ebitda * (1 + revenue_cagr)^hold_years
earnings the year you sell, after the whole hold period of growth
exit_ev = exit_ebitda * exit_multiple
what the company sells for at exit
remaining_debt = max(debt - debt * amort_pct * hold_years, 0)
debt still owed at exit, paid down at a steady rate
exit_equity = exit_ev - remaining_debt
what you walk away with when you sell
moic = exit_equity / equity
multiple of your cash back -- $1 in, this many dollars out
gross_irr = moic^(1 / hold_years) - 1
the annual return that would turn your investment into that multiple over the hold
Real Estate 15 formulas
gross_rent = monthly_rent * 12
a full year of rent at full occupancy
vacancy_loss = gross_rent * vacancy_rate
rent you lose to empty months
effective_income = gross_rent - vacancy_loss
the rent you actually collect after vacancies
noi = effective_income - operating_expenses
net operating income -- rent collected minus running costs
cap_rate = noi / purchase_price
the unleveraged yearly return, as a percent of price
gross_rent_multiplier = purchase_price / gross_rent
how many years of gross rent the price equals
price_per_sqft = purchase_price / square_feet
the price per square foot, for comparing properties
loan_amount = purchase_price * ltv
how much you borrow, set by the loan-to-value
down_payment = purchase_price - loan_amount
the cash you put down to buy
monthly_mortgage = loan_amount * (monthly_rate * (1 + monthly_rate)^num_payments) / ((1 + monthly_rate)^num_payments - 1)
the level monthly loan payment (standard amortization)
annual_debt_service = monthly_mortgage * 12
a full year of mortgage payments
dscr = noi / annual_debt_service
whether the rent covers the loan (lenders want above ~1.2)
annual_cash_flow = noi - annual_debt_service
the cash left in your pocket each year after the loan
cash_on_cash = annual_cash_flow / cash_invested
the yearly cash return on the actual cash you put in
breakeven_occupancy = (operating_expenses + annual_debt_service) / gross_rent
how full you must keep it just to cover the bills
Discounted Cash Flow 6 formulas
cost_of_equity = risk_free_rate + beta * market_premium
the return shareholders demand for taking on the risk (CAPM)
wacc = equity_weight * cost_of_equity + debt_weight * cost_of_debt * (1 - tax_rate)
the blended cost of all the money funding the business
present_value = cash_flow / (1 + wacc)^year
what a future payment is worth in today's dollars
terminal_value = final_cash_flow * (1 + growth) / (wacc - growth)
the value of all cash flows after the forecast, growing forever
terminal_value_today = terminal_value / (1 + wacc)^forecast_years
that far-future value pulled back to today's dollars
profitability_index = present_value / cash_flow
value created per dollar invested (above 1 is good)
Bond Math 6 formulas
coupon_payment = face_value * coupon_rate
the fixed dollar interest the bond pays each year
current_yield = coupon_payment / price
the income you earn as a percent of what you paid
capital_gain_per_year = (face_value - price) / years_to_maturity
the yearly pull toward face value as the bond matures
yield_to_maturity = (coupon_payment + capital_gain_per_year) / ((face_value + price) / 2)
an estimate of your total return if you hold to the end
price_change = -modified_duration * price * yield_change
how much the bond's price moves when interest rates shift
premium_or_discount = price - face_value
how much above or below face value the bond trades
Motion (Kinematics) 8 formulas
final_velocity = initial_velocity + acceleration * time
speed at the end after speeding up (or slowing) for a while
average_velocity = (initial_velocity + final_velocity) / 2
the mid-point speed when acceleration is steady
distance = initial_velocity * time + 0.5 * acceleration * time^2
how far you travel while steadily speeding up
momentum = mass * final_velocity
mass in motion -- how hard it is to stop
force = mass * acceleration
the push needed to produce that acceleration (Newton's second law)
kinetic_energy = 0.5 * mass * final_velocity^2
the energy stored in the motion
work_done = force * distance
energy delivered by pushing over a distance
power = work_done / time
how fast the work gets done
Heat & Thermal 6 formulas
heat_energy = mass * specific_heat * temp_change
energy needed to change a material's temperature
temp_diff = hot_temp - cold_temp
the temperature gap that drives heat to flow
thermal_resistance = thickness / (conductivity * area)
how strongly a wall resists heat passing through it
heat_rate = temp_diff / thermal_resistance
how fast heat flows through the wall, in watts
cooling_rate = cooling_coefficient * area * (hot_temp - ambient_temp)
how fast a hot object sheds heat to the room (Newton's cooling)
r_value = thickness / conductivity
insulation rating -- higher means it keeps heat in better
Electrical Circuits 7 formulas
voltage = current * resistance
the electrical push across a resistor (Ohm's law)
power = voltage * current
how fast the circuit uses energy, in watts
energy_used = power * time
total energy consumed while it runs
series_resistance = first_resistance + second_resistance
resistors in a line just add up
parallel_resistance = (first_resistance * second_resistance) / (first_resistance + second_resistance)
resistors side by side give a smaller combined resistance
capacitor_energy = 0.5 * capacitance * voltage^2
energy banked in a charged capacitor
charge = capacitance * voltage
how much charge the capacitor holds
Fluids 6 formulas
pressure = force / area
force spread over an area -- push per square metre
hydrostatic_pressure = density * gravity * depth
the squeeze from the weight of fluid above you
flow_rate = area * velocity
volume of fluid passing a point each second
dynamic_pressure = 0.5 * density * velocity^2
the pressure a moving fluid exerts by its motion
reynolds_number = density * velocity * pipe_diameter / viscosity
a dial: low means smooth flow, high means turbulent
buoyant_force = density * gravity * displaced_volume
the upward lift a fluid gives a submerged object
Beam & Structures 6 formulas
max_bending_moment = load * length / 4
the strongest bending the beam feels, at its centre
section_modulus = moment_of_inertia / half_depth
how much the beam's shape resists bending
max_stress = max_bending_moment / section_modulus
the peak pull on the material -- compare to its strength
max_deflection = (load * length^3) / (48 * elastic_modulus * moment_of_inertia)
how far the middle of the beam sags under the load
stiffness = (48 * elastic_modulus * moment_of_inertia) / length^3
how much load it takes to push the beam down by one unit
safety_factor = yield_strength / max_stress
how much headroom before the material fails (want it above 1)
Control Systems 6 formulas
time_constant = 1 / cutoff_frequency
how long the system takes to make most of a move
settling_time = 4 * time_constant
roughly how long until it stops moving and settles
natural_frequency = sqrt(stiffness / mass)
the speed the system would oscillate at on its own
damping_ratio = damping / (2 * sqrt(stiffness * mass))
how quickly wobbles die out (1 is just-right, below 1 overshoots)
damped_frequency = natural_frequency * sqrt(1 - damping_ratio^2)
the actual wobble speed once damping slows it down
percent_overshoot = 100 * exp(-damping_ratio * 3.14159 / sqrt(1 - damping_ratio^2))
how far past the target it shoots before settling, as a percent
Optimization
Set objectives, input ranges, and discrete choices. Inspect the best result and its certificate.
maximizeName what to make as big as possible.
maximize variable
Name what to make as big as possible. Pair it with assume knobs (the dials Cast may turn) and Cast searches the whole range for the settings that achieve the best value -- the global optimum, with a certificate. The answer renders in its true shape: a number for one dial, a 2D map for two. Use minimize to make something as small as possible instead. Your formulas must be in a relationships: block (not plain assignments) for the optimizer to see them.
Find the price that maximizes profit
relationships: cups = 500 - 8 * price profit = price * cups - 200 assume price from 1 to 60 maximize profit
Optimal Answer: 7,612.50 at price = 31.25
Change the upper bound from 60 to 25 — the optimum shifts to fit the new constraint.
minimizeMake a result as small as possible -- cost, risk, waste.
minimize variable
Make a result as small as possible -- cost, risk, waste. Works exactly like maximize but in the other direction. Name two or more objectives (e.g. maximize profit AND minimize risk) and Cast returns the trade-off frontier: the set of best balances, since there is no single winner.
Two competing goals -> a trade-off frontier
relationships: profit = 100 - 2 * cost quality = cost assume cost from 0 to 40 maximize profit minimize quality
Pareto frontier: every point is best at some balance
assume ... from ... toDeclare a dial Cast may turn, and the range it may turn it within.
assume knob from low to high
Declare a dial Cast may turn, and the range it may turn it within. The optimizer searches this range for the best setting. List several on one line, comma-separated -- no hard limit on knob count. Formulas must be in a relationships: block -- plain variable assignments are not visible to the optimizer. Answer shape scales with knob count: 1 knob -> curve, 2 knobs -> heatmap, 3+ knobs -> 2D slices through the optimum for the widest-range pairs. (Bare "assume knob" with no range = Boundary Solver: Cast auto-searches up to 1,000,000 to find the covenant floor.)
Two dials -> a 2D answer with the optimum pinned
relationships: profit = 100 * price - 2 * price * price + 60 * promo - 3 * promo * promo assume price from 0 to 50, promo from 0 to 20 maximize profit
Optimal Answer: 1,550 at price = 25, promo = 10
chooseLet Cast pick the best option from a fixed list -- a financing structure, a tier, an on/off switch.
choose name from [a, b, c]
Let Cast pick the best option from a fixed list -- a financing structure, a tier, an on/off switch. Cast re-optimizes for each option and keeps the winner. A yes/no switch is just a two-item list: choose pik from [on, off].
Pick the option that maximizes profit, alongside a dial
relationships: profit = base * units - 2 * units * units assume units from 0 to 50 choose base from [10, 20] maximize profit
base = 20, units = 5, profit = 50
Monte Carlo
Run thousands of simulations with random inputs to see the range of possible outcomes. Great for risk analysis.
event timingRandom event timing applies an amount in one whole forecast period chosen once per simulation.
event = amount in any year from first to last
Random event timing applies an amount in one whole forecast period chosen once per simulation. All periods have equal chances, including both endpoints. Also: event = amount in year first to last. An ordinary Cast shows one sampled path. Works with months, weeks, days or generic periods; bounds must fit the forecast. Both accounts can reference one event row to share the crash. For fixed ordinary timing, declare event = amount in year 10 and add event occurs in year first to last inside Monte Carlo.
Random crash timing with yearly withdrawals
forecast 18 years: market_crash = -30% in any year from 1 to 18 balance starts at $400K, then previous balance * (1 + market_crash) - $10K ending_balance = final(balance) | result @montecarlo runs: 1000 show: ending_balance
1,000 ending balances with one crash in each path
fat_tailsDefine symmetric uncertainty with more extreme outcomes than a bell curve.
fat_tails(center, spread, degrees = 5)
Define symmetric uncertainty with more extreme outcomes than a bell curve. Spread is the standard deviation. Also: student_t(center, spread, degrees). Degrees defaults to 5; values closer to 2 create heavier tails. Degrees must exceed 2 so spread is defined. Call sample() for a fresh draw.
Returns with fat tails
stock_returns = fat_tails(7%, 4%) yearly_return = sample(stock_returns)
A fresh return with 7% average and 4 percentage-point standard deviation
custom_distributionDefine your own possible numeric outcomes and their probabilities.
custom_distribution(values, probabilities)
Define your own possible numeric outcomes and their probabilities. Supply one nonnegative probability per outcome, totaling 100%. Omit probabilities to sample observed or historical values equally with replacement. Duplicate observations retain their frequency. Also: discrete(values, probabilities). Call sample() for a fresh draw. Resampling individual observations does not preserve the original historical order.
Manual return probabilities
stock_returns = custom_distribution([-30%, 7%, 20%], [10%, 70%, 20%]) yearly_return = sample(stock_returns)
A 10% chance of -30%, 70% chance of 7%, and 20% chance of 20%
Sample observed returns
observed_returns = [-10%, 5%, 12%] stock_returns = custom_distribution(observed_returns) yearly_return = sample(stock_returns)
Each observation has an equal chance on every draw
sampleDraw a fresh number from a named distribution.
sample(distribution)
Draw a fresh number from a named distribution. In a forecast, a sampled row draws once each period. Reference that row from both accounts to share the same market return. Monte Carlo repeats the entire path. An ordinary Cast shows one random path; it is not an expected-value forecast.
Fresh market returns for each of 18 years, repeated 1,000 times
stock_returns = bell_curve(7%, 4%) starting_balance = $200K annual_contribution = $7K forecast 18 years: yearly_return = sample(stock_returns) balance starts at starting_balance, then previous balance * (1 + yearly_return) + annual_contribution ending_balance = final(balance) | result @montecarlo runs: 1000 show: ending_balance
18 yearly draws per path; 1,000 ending balances
bell_curveDefine a reusable bell curve.
bell_curve(center, spread)
Define a reusable bell curve. Spread means one standard deviation, not a hard bound. Also: normal(center, spread). Call sample() to draw a number. Inside @montecarlo, existing normal() distribution lines still draw one input per run.
Define returns, then draw one
stock_returns = bell_curve(7%, 4%) yearly_return = sample(stock_returns)
A return drawn from the defined bell curve
equal_rangeDefine a bounded range with equal probability throughout.
equal_range(minimum, maximum)
Define a bounded range with equal probability throughout. Also: uniform(minimum, maximum). Call sample() for a fresh draw.
Choose a crash year
crash_years = equal_range(0.5, 18.5) crash_year = round(sample(crash_years))
One integer year from 1 through 18
likely_rangeDefine a bounded range peaked at the likely value.
likely_range(minimum, likely, maximum)
Define a bounded range peaked at the likely value. Also: triangular(minimum, likely, maximum). Call sample() for a fresh draw.
Define uncertain inflation
inflation_choices = likely_range(1%, 2.5%, 5%) inflation = sample(inflation_choices)
A draw between 1% and 5%
skewed_positiveDefine a positive, skewed distribution from its underlying log-space center and spread.
skewed_positive(log_center, log_spread)
Define a positive, skewed distribution from its underlying log-space center and spread. Also: lognormal(log_center, log_spread). These parameters describe logarithms, not the final arithmetic average. Call sample() for a fresh draw.
Define a positive multiplier
multipliers = skewed_positive(0, 0.3) multiplier = sample(multipliers)
A positive random multiplier
@montecarloMonte Carlo simulation block - run your model thousands of times with random inputs and event timings to see possible outcomes.
@montecarlo
Monte Carlo simulation block - run your model thousands of times with random inputs and event timings to see possible outcomes. The block contains runs:, optional distribution or event timing lines, and show:. Cast re-runs your WHOLE model each simulation. Define calculations in the model ABOVE the block and list existing output variables in show:. Models using sample() or inline random timing need only runs: and show:.
Simulate investment returns with uncertain rate and growth
investment = -100000 cashflow = 30000 years = 5 rate = 8% growth = 3% final_cf = cashflow * (1 + growth) ^ years profit = npv(rate, cashflow, cashflow, cashflow, cashflow, final_cf) + investment @montecarlo runs: 1000 rate = 8% +/- 2% growth = 2% to 5%, likely 3% show: profit, final_cf
Distribution histogram + summary stats for profit and final_cf
Change runs: 1000 to runs: 100 — output gets noisier. Try 10000 — smoother curve, slower.
@sensitivitymatrixSensitivity matrix - test every combination of two variables and see results in a 2D heatmap grid.
@sensitivitymatrix
Sensitivity matrix - test every combination of two variables and see results in a 2D heatmap grid. Click any cell to load those values. Also: @whatif, @sensitivity-matrix.
How does revenue change across price and quantity?
@sensitivitymatrix rows: quantity = [30, 50, 70] cols: price = [80, 100, 120] show: revenue
3x3 heatmap grid of revenue for each combo
bell curve (+/-)Bell curve (normal distribution) - most values near the center, fewer at extremes.
var = center +/- spread
Bell curve (normal distribution) - most values near the center, fewer at extremes. Use for inputs where you know the average and typical spread (interest rates, growth rates, market returns). Also accepts "plus or minus".
Interest rate centered at 8% with 2% spread
rate = 8% +/- 2%
Samples mostly between 4% and 12%
Same thing, written out
rate = 8% plus or minus 2%
Samples mostly between 4% and 12%
Classic syntax (also works)
rate = normal(8%, 2%)
Identical behavior
Change +/- 2% to +/- 5% — watch the confidence band widen as uncertainty grows.
range with peak (to/likely)Three-point estimate (triangular distribution) - you specify the minimum, most likely, and maximum values.
var = min to max, likely mode
Three-point estimate (triangular distribution) - you specify the minimum, most likely, and maximum values. Use when you have expert judgment but not statistical data.
Growth rate: worst 2%, likely 3%, best 5%
growth = 2% to 5%, likely 3%
Samples concentrated around 3%
Classic syntax (also works)
growth = triangular(2%, 3%, 5%)
Identical behavior
Shift the likely: value toward min or max — watch the output histogram skew to follow it.
flat range (to)Equal probability across a range (uniform distribution) - every value equally likely.
var = min to max
Equal probability across a range (uniform distribution) - every value equally likely. Use when you only know the bounds, not the shape.
Price could be anywhere between 80 and 120
price = 80 to 120
Samples evenly spread between 80 and 120
Classic syntax (also works)
price = uniform(80, 120)
Identical behavior
Narrow the range (e.g. 90 to 110) — output tightens too. Widen it and watch spread grow.
lognormal distributionAlways-positive, right-skewed distribution.
var = lognormal(mean, stdev)
Always-positive, right-skewed distribution. Use for quantities that cannot go negative and have occasional large values (stock prices, project costs, incomes).
Cost multiplier (always positive, sometimes high)
cost_mult = lognormal(0, 0.3)
Samples centered around 1.0, skewed right
Units
Physics and engineering units with automatic dimensional analysis. Multiply kg by m/s^2 and get Newtons.
Whole-currency displayLarge amounts in cards, calculations and charts use K (thousands), M (millions), B (billions) or T (trillions).
revenue = $5.342M | result
Large amounts in cards, calculations and charts use K (thousands), M (millions), B (billions) or T (trillions). Forecast tables share a labeled unit such as Amounts in millions. Percentages and ratios stay unchanged; calculations always use the full amount.
Readable result, exact calculation
revenue = $5.342M | result profit = revenue * 25%
Revenue: $5.34M. Profit: $1.34M. The actual profit remains $1,335,500.
convertConvert between compatible units (length, mass, temperature, etc.).
convert(value, target_unit)
Convert between compatible units (length, mass, temperature, etc.).
Force in different units
force_lbf = convert(100 N, lbf)
22.48 lbf
Temperature conversion
temp_f = convert(20 degC, degF)
68 degF
Try convert(100 N, lbf) for force, or convert(37 degC, degF) for temperature.
Unit MathCast does dimensional analysis automatically - units multiply and divide correctly.
value1 unit1 * value2 unit2
Cast does dimensional analysis automatically - units multiply and divide correctly.
Force = mass × acceleration
force = 10 kg * 9.8 m/s^2
98 N (auto-converts)
Speed = distance / time
speed = 100 m / 10 s
10 m/s
dbm_to_wConvert decibels-milliwatt (dBm) to watts.
dbm_to_w(dBm)
Convert decibels-milliwatt (dBm) to watts. Common in RF/telecom.
WiFi transmitter power
power = dbm_to_w(20)
0.1 (100 mW)
w_to_dbmConvert watts to decibels-milliwatt (dBm).
w_to_dbm(watts)
Convert watts to decibels-milliwatt (dBm). Inverse of dbm_to_w.
Express 1 watt in dBm
level = w_to_dbm(1)
30
free_space_path_lossHow much signal weakens over distance at a given frequency, in dB.
free_space_path_loss(distance_km, freq_ghz)
How much signal weakens over distance at a given frequency, in dB.
5 GHz WiFi at 100 meters
loss = fspl(0.1, 5)
86.43 dB
link_budgetRF link budget - calculates received signal power.
link_budget(tx_power, tx_gain, rx_gain, path_loss)
RF link budget - calculates received signal power. Result = tx_power + tx_gain + rx_gain - path_loss.
Point-to-point microwave link
signal = link_budget(30, 20, 20, 120)
-50 dBm
AI Discovery
Fit curves, compare predictive models, and search for readable equations.
Word-based text classificationTry Data → Sample datasets → SMS spam — training for 3,877 labeled messages from the video.
build model to predict spam
Try Data → Sample datasets → SMS spam — training for 3,877 labeled messages from the video. These SMS samples replace the older website classifier demo pairs. Run the one-line command below. Cast detects message text and automatically separates training and test rows. Spam and Legit appear in the matrix and predictions. Then load SMS spam — new messages for ten separate messages without answers. The fitted model and its word reader stay ready for reuse.
Learn to detect spam
build model to predict spam
Cast detects message text and selects word learning automatically.
Choose inputs
build model to predict cancel from note, monthly_spend, country
Words, numbers and category inputs share one saved classifier.
Override ambiguous text
build model to predict urgent from subject as text
Explicitly learn words in short or repeated subjects.
Classification only. Cast detects varied sentences from training rows and shows the selected text columns. Short/repeated names stay categories; as text overrides this for ambiguous text. Each text cell needs nonblank text. Vocabulary learns training rows only, including each validation fold. Need at least 200 labeled rows, including 20 of each outcome in training and test. Text + prediction speed includes word reading and predictions; loading, network and screen time remain excluded. Replace discovery with predict spam for rows 1 to 10 to reuse it.
Model prediction speedThe quality card shows milliseconds per result (batch average), total milliseconds and count beside accuracy or fit.
build model to predict target from inputs; predict target for rows 1 to 10
The quality card shows milliseconds per result (batch average), total milliseconds and count beside accuracy or fit. Training uses untouched test records; predict shows the current batch, while accuracy retains its original test scope. Includes prediction and probabilities; word-model summaries also include reading text. Excludes training, loading, network and screen time. Missing timing says Not available.
Measure new predictions
predict revenue for rows 1 to 10
Results show measured model time for ten predictions.
A single result run alone can take a different amount of time. Word-based models also show Text + prediction speed: word reading plus prediction/probabilities, excluding loading, transport and display.
Overall model qualityThe trained-model banner shows Test accuracy for categories, or Prediction fit (explained variation, R²) and Prediction error (RMSE) for numbers.
build model to predict target from inputs
The trained-model banner shows Test accuracy for categories, or Prediction fit (explained variation, R²) and Prediction error (RMSE) for numbers. Scores use original held-out test rows. A negative fit is worse than predicting the average. Missing scores say Not available.
Train and inspect the overall model score
build model to predict revenue from spend
Overall model quality appears above the detailed results.
The original score remains after saving, loading or predicting new rows. It does not guarantee accuracy on new data.
predictReuse a trained model on new rows without retraining.
predict target for row 1 predict target for rows 1 to 10
Reuse a trained model on new rows without retraining. First run build model to predict, then replace that command with predict, load a new CSV in Data and click Cast. Keep the same input column names and units. The known outcome column is not required; extra columns are allowed.
Predict revenue for two new customers
predict revenue for rows 1 to 2
Each new row appears beside its prediction in Output.
Also: rows 1:10 (same thing). Save the Cast model to retain the trained model and results for another session. Export CSV downloads every requested prediction. Run build model to predict again only when you want to retrain. In Data, Sample datasets is the last toolbar action. Datasets stays visible and asks you to sign in for saved datasets.
| fitTrigger automatic curve fitting.
variable = array_values | fit
Trigger automatic curve fitting. Cast tries polynomial, power law, exponential, logarithmic, and linear fits against all other array variables in the model.
Fit y against x automatically
x = [1, 2, 3, 4, 5] | hidden y = [2.1, 4.0, 8.9, 16.2, 25.1] | fit
Ranked fit cards: R², RMSE, AIC per equation
Fit experimental readings
time = [0, 1, 2, 3, 4] | hidden concentration = [100, 67, 45, 30, 20] | fit
Best fit shown in chart with curve overlay
Change y values to [2, 8, 32, 128] -- fits an exponential. Try [1, 4, 9, 16] for a parabola.
discoverAI-powered equation discovery.
discover target from predictor1, predictor2
mode = auto | guided
max attempts = NAI-powered equation discovery. Streams iterations live — AI proposes equations, solver fits them, best candidates are ranked by R²/AIC. Use // comments above the block to give the AI domain hints.
Basic discovery
x = [1, 2, 4, 8] | hidden y = [2, 8, 32, 128] | hidden discover y from x
AI finds y = 2 * x^2 (or similar)
Guided mode with domain hint
// Arrhenius kinetics expected
discover rate from temperature
mode = guided
max attempts = 15Pauses after each attempt for your approval
Change mode to guided -- pause after each iteration to approve or skip. Add a // domain hint above the block.
build modelShort form: build model target.
build model to predict target [from col1, col2, ... | from all]
Short form: build model target. Also: discover model target (same thing). Automatically trains and compares an explainable boosting model (EBM) and a linear model. Picks the best by AUC for categories or R2 for numbers, and shows which factors matter most. Omit "from" or use "from all" to use all columns except the target.
Churn prediction (all columns)
build model to predict churn
Uses all columns except churn as predictors. Verdict: EBM won. AUC 0.91.
Churn prediction (specific columns)
build model to predict churn from tenure, spend, tickets
Verdict: EBM won. AUC 0.91. Factors ranked by importance.
Cast splits before learning, compares EBM and linear using training folds, then tests on untouched records. Need at least 200 labelled rows, including 100 training and 100 test rows. Classification needs at least 20 per outcome in each set. Bad model means do not use its predictions. Passing supports exploration of similar records. Numbers and categories can be predictors; their mappings use training records only.
build equationShort form: build equation target.
build equation to predict target [from col1, col2 | from all]
max attempts = NShort form: build equation target. Also: discover equation target (same thing). Find a readable equation using training records, then test it on records kept aside first. Output leads with a use verdict, AI analysis and a Prediction check chart. Omit "from" to use every other column.
Find spending equation (all columns)
build equation to predict monthly_spend
Best: spend = 12.3 * ln(tenure) + 45.6 (R2 = 0.94)
Read the test verdict first. Need 200 labelled records, at least 100 training and 100 test records; classification needs 20 per outcome in each set. Bad model means do not use predictions. Binary equations include a Confusion Matrix of untouched test records: correct negatives, caught cases, missed cases and false alarms at a 50% cutoff. Numerical equations use Prediction check instead. R² is not classification accuracy. Display uses 3 significant digits; Exact equation keeps calculation precision. Other equations tried shows training alternatives.
AI Research
Ask a question about your data and let the AI agent run a multi-step investigation automatically.
research_modeAsk a question about your data.
Research Mode (guide)
Ask a question about your data. Each step states its goal, runs a calculation and shows Supported, Disproved or Inconclusive with the reason. Ask AI opens a question about that step; the assistant receives all saved calculations. The final research answer comes first, followed by evidence for each goal. Mermaid diagrams shrink to fit their frame; Enlarge opens a larger view. Upload a CSV or choose a sample from Research with AI.
Step 1: Click Research with AI, type your question
"What drives crop yield?"
Agent breaks this into testable criteria
Step 2: Agent proposes code, you click Run
result = top_predictors(data.yield)
temperature: r=0.64 (strong), humidity: r=0.37 (moderate)
Step 3: Agent digs deeper based on findings
hot = data.yield where data.temperature > 77 cold = data.yield where data.temperature <= 77 result = ttest(hot, cold)
p=0.00003 -- significant difference
Step 4: Agent builds a model when ready
build model to predict yield from all
Predictive model with R-squared and feature importance
Click Research with AI and type your question — the agent breaks it into testable steps. Crop yield and marketing allocation demos include saved, editable goals for zero credits. AI research calls begin after Start Research.
research_criteriaResearch starts with 2-4 testable goals.
Research Criteria (guide)
Research starts with 2-4 testable goals. Edit, add or remove them before starting. Each goal gets a verdict and reason: Supported, Disproved or Inconclusive. Inconclusive means the available evidence cannot support or disprove the claim. A correlation alone does not prove a cause or the best spending decision.
Example criteria for "What drives crop yield?"
1. Is temperature the strongest predictor? 2. Is there a soil pH threshold effect? 3. Do humidity and rainfall contribute independently?
Each gets checked off as the agent finds evidence
auto_runManual mode pauses after each calculation and evidence assessment.
Auto-Run Mode (guide)
Manual mode pauses after each calculation and evidence assessment. Choose Next step to continue, or enable Auto-run. Pause and Turn off auto finish the current request and prevent later steps. Each step shows credits used; the shared balance sits at upper right. Stop rejects late replies. Repeated calculation errors and duplicate proposals stop automatic work.
Typical auto-run session
Check auto-run box -> agent runs 5-10 steps -> pauses when done
Review each goal and verdict; actual credits are shown for every step.
App Features
Read, edit, organize, share, and export your work.
Read images in AssistantPaste a screenshot or photo, or attach PNG, JPEG or WebP through the paperclip.
Assistant → paste image → review → Send
Paste a screenshot or photo, or attach PNG, JPEG or WebP through the paperclip. Review or remove the preview before Send. Ask about a chart, document image or screenshot. Images remain in saved chats and follow-up questions. Nothing sends automatically. Choose an image-capable model such as Claude, GPT or Gemini.
Read a chart
Paste a chart, then ask: What changed between these two years?
The assistant receives the actual image and your question.
Up to ten images totaling 2 MB per chat request. Start a new chat when older images fill this limit. Paste DOCX images separately.
Read local files in AssistantPaste CSV text, Excel cells or document text.
Assistant → paste or Attach file → review → Send
Paste CSV text, Excel cells or document text. Attach CSV, Excel, ODS, Word (.docx) or plain text. Copied files also paste when your browser supplies them. Filenames and sheet names stay in your editable draft. Nothing sends automatically. DOCX reads text and tables, not images; save old .doc files as .docx or paste their text.
Read copied spreadsheet cells
Paste cells, then ask: Which items have the highest amount?
The assistant receives the pasted rows and columns.
Up to ten files totaling 5 MB, including expanded archives; at most 200,000 draft characters. Failed reads keep your draft.
Inspect assistant thinkingOpen the caret to see readable thinking returned by the selected provider.
Thinking or Thought → caret
Open the caret to see readable thinking returned by the selected provider. It is saved with that reply on this browser. The connecting orb animates while a reply runs and becomes still with reduced motion. Some providers do not return readable thinking; Cast says so when the pending panel is opened.
Inspect a reply
Click Thought above the answer
The returned trace expands as plain text
Named sharing addressesSignup asks for a chosen public username and optional full name.
Share → choose an owner and model name → Create link → Copy link
Signup asks for a chosen public username and optional full name. Personal links use cast.is/username/model-name. Organization administrators can choose cast.is/org/organization/model-name. Names only need to be unique within their owner. Email stays private. Old usernames and existing random links keep working.
Name a personal model
Username: russell; model address name: acquisition-model
cast.is/russell/acquisition-model
Gmail and other known personal providers default to Personal. A saved link appears before you copy it. Anonymous sharing retains temporary random links.
Themed notices and confirmationsData replacement, clearing and deletion use a modal that follows your theme.
Read the confirmation → Cancel or Continue
Data replacement, clearing and deletion use a modal that follows your theme. Cancel leaves the existing data intact. Assistant and voice errors use the same modal instead of browser alerts.
Keep the current dataset
Data → Clear → Cancel
Your data stays in the grid
Escape cancels. Requests wait their turn so one notice cannot erase another.
Join your email-domain organization automaticallyVerified company email domains get one organization automatically.
Verify your email → join its domain organization
Verified company email domains get one organization automatically. Accounts with the same exact company domain join as members. Known personal providers such as Gmail stay personal. Your personal models stay private. Changing your verified company domain removes access to the old domain organization.
Two verified accounts with the same domain
alex@company.com and sam@company.com
Both join the automatic company.com organization as members
No domain approval is needed. Subdomains are separate organizations. Joining does not grant administrator permissions, publish personal models or change your plan.
Private collaborationA paid owner saves a model, then invites a verified Cast account by email.
Share → Collaborate → Invite editor
A paid owner saves a model, then invites a verified Cast account by email. The recipient accepts from Load. Free accounts can edit invited models. Edits reach others after Save. Only the owner manages access.
Work on a budget together
Save → Share → Collaborate → enter colleague email
Your colleague accepts and edits the same saved model
Stale saves keep your draft. Compare latest, Load latest or Save copy. Private trained-model handles and server datasets must be removed before inviting; inline imported data is supported.
CSV uploadImport your CSV, or click Data → Sample datasets.
Data → Import CSV → Editor → Cast
Import your CSV, or click Data → Sample datasets. Website phishing — training has 800 website records with answers; Website phishing — prediction has six separate records without answers. Train first, then load the prediction sample to reuse that model. 1 means phishing; 0 means legitimate. Historical input codes describe security checks, not raw measurements. Marketing and crop samples are also available. Cast asks before replacing populated data.
Analyze a loaded column
total_sales = sum(sales)
The total appears in Output; imported rows remain in Data
The notice disappears after four seconds. Hovering or focusing its close button keeps it visible.
Minimize data instructionsAfter loading data, the panel below the editor lists column names and commands to copy.
Editor: Data Grid Commands → Minimize → Show instructions
After loading data, the panel below the editor lists column names and commands to copy. Click its full header to minimize it to one bar and give your model more room. Click Show instructions to reopen it. Your grid and model stay unchanged.
Make room to write
Load data, switch to Editor, click Minimize
Instructions fold to one bar; Show instructions reopens them
Enter or Space toggles the focused header. Cast remembers your choice between visits.
Find real data with the assistantFind data covers websites, APIs and public datasets.
Ask for a dataset or a table from a public page
Find data covers websites, APIs and public datasets. CSV, tab-separated text, JSON, spreadsheets, GitHub file pages, download links and simple page tables load directly without extraction credits. Other page tables use Firecrawl; Alexandria supplies provider datasets. Every web search uses Exa. Verified rows load into Data with editable row labels and a source receipt. Cast asks before replacing existing data. CSV uploads and spreadsheet paste preserve quoted multiline cells and blank values. Follow-up choices use the current model and add no AI calls.
Find data to analyze
Ask: "Find an official population dataset and prepare a small table for Data."
A source-backed table ready to load into the Data tab
Give the page URL, desired columns and number of rows. Cast has no fixed row-count cap; source page limits, prices and response-size limits still apply. Server keys and provider access are required. Missing data is never invented.
Terms of Service and Privacy PolicyThe rules for using Cast, and what Cast does with your data.
Terms and Privacy links in the site footer and under the sign-in form
The rules for using Cast, and what Cast does with your data. Your saved models stay yours. Cast keeps the questions you ask its AI features and the models it generates, and uses them for analysis and to train AI models. Cast does not sell personal information.
Read what Cast collects and why
Open cast.is/privacy
What is collected, who it is shared with, and how to ask for deletion
Questions or deletion requests: russell@zavient.com.
Clear editor (+ New)Wipes the editor and results back to a blank slate.
+ New button (editor toolbar)
Wipes the editor and results back to a blank slate. Clears code, scenarios, overrides, loaded data, and research state. Use it when you want to start a brand-new model from scratch without picking a template.
Start fresh
Click "+ New" in the editor toolbar
Editor empty, results gone, ready for a new model
Want a starting point instead? Click the smaller "Templates" button right next to + New.
Diagrams in AI answersAsk the assistant to draw a flow or relationship.
AI assistant: Diagram → Enlarge → Close
Ask the assistant to draw a flow or relationship. Mermaid blocks render as diagrams in chat, with no Paste button; text diagrams also work. Replies use plain English and useful emoji signposts. Every model starts at medium effort for chat, model generation and troubleshooting; it can adjust effort for a harder next call using supported provider controls, which uses credits. Stop replaces Send while a reply is running, so you can cancel, correct your prompt and resend.
Ask how something works
Ask the assistant: "How does a forecast with previous and starts at work?"
A bold one-line answer, a few bullets, and a small ↓ diagram of period 1 feeding period 2
Ask the assistant to "draw it". Diagrams follow your theme and Enlarge opens a larger view. Rejected model code gets three repair retries with the exact Cast errors. Each repair uses credits. Copy debug retains rejected code and every attempt, including after reopening the chat.
Personality and memorySave your background, preferences and how Cast should talk.
Assistant header: Personality and memory
Save your background, preferences and how Cast should talk. Details stay with your account on this browser across reloads and new chats. Say Remember that… in chat to save a preference without another AI call. Review, edit or remove details in the same window. Cast uses useful emoji and Mermaid diagrams on its own when they help.
Remember a useful preference
Remember that I prefer monthly forecasts
A saved preference you can review or remove in Personality and memory
Click Save preferences to keep changes. Cancel leaves your saved details unchanged.
Guide beside your resultsThe Guide opens as a column beside your results, so you can read a lesson, type it into the editor and see the result without closing anything.
Guide button (results toolbar)
The Guide opens as a column beside your results, so you can read a lesson, type it into the editor and see the result without closing anything. Close it and open it again and it comes back to the same spot, even after a reload.
Read and build side by side
Click "Guide", scroll to a lesson, copy an example into the editor, Cast
Editor, results and Guide all visible at once
Want more room to read?
Click the expand arrows at the top of the Guide
The Guide covers the results area; click again to put it back beside them
Beside or expanded is remembered between visits.
Theme SwitcherChange the app's color theme -- click the icon to pick one, or press Ctrl+D to cycle through them.
Paint-bucket icon (toolbar) or Ctrl+D to cycle
Change the app's color theme -- click the icon to pick one, or press Ctrl+D to cycle through them. Your choice is saved and changes chart colors, the wand, contrails, browser favicon and assistant icons. Cloud uses a brown wand, gold trails and an ivory favicon; other themes have matching colors. Phone and installed desktop icons keep gold and brown. Assistant text is readable as each word arrives.
Pick a theme
Click the paint-bucket icon in the toolbar
Theme picker opens; choose one to apply it instantly
Ctrl+D cycles to the next theme without opening the picker.
TemplatesOpens worked examples for finance, business, science and backsolve.
Templates button (editor toolbar)
Opens worked examples for finance, business, science and backsolve. The starter and every personal-finance/finance-business worked template include forecast tables and charts. Future answers come from their forecast rows: loan payoff, savings, rent/buy equity, earnings, recurring revenue, online sales, marketing, real estate, acquisitions and cash planning.
Pick a template
Click "Templates" in the editor toolbar
Template picker opens; choose one to load it into the editor
Choose SaaS Growth and click Cast: month 1 starts at $50,000, adds 20% growth and removes 5% churn, ending at $57,500. Change Projection Period to adjust its months. Marketing repeats a constant monthly plan. Click "+ New" for a blank editor.
Keyboard ShortcutsSpeed up your workflow with keyboard shortcuts.
Cmd+Enter / Ctrl+Enter
Speed up your workflow with keyboard shortcuts. Available anywhere in the app.
Cast (evaluate) your model
Cmd+Enter (Mac) or Ctrl+Enter (Windows)
Evaluates model instantly
Save model
Cmd+Shift+S (Mac) or Ctrl+Shift+S (Windows)
Saves to cloud
Jump to section
Ctrl+Shift+1 through Ctrl+Shift+9
Scrolls to that section in results
Try Ctrl+Enter right now — same as the Cast button. Ctrl+Shift+S saves without reaching for the mouse.
Share LinkTurn the model into a link anyone can open in a browser — no install and no account on their end.
Share → Share Snapshot / Share View / Embed
Turn the model into a link anyone can open in a browser — no install and no account on their end. If the model is saved to your account the link follows it, so a formula you fix today reaches whoever is holding yesterday's link. Share an unsaved draft instead and the link keeps a frozen copy, because there is no saved model behind it to follow. The page tells the reader which one they have: Live means the numbers track yours, Snapshot means they are a copy from the moment you shared.
Share a saved model
Share → Share Snapshot
Choose a model address, then Create link. A Snapshot freezes the source you share.
Share an unsaved draft
Share → Share Snapshot without saving first
A named link marked Snapshot keeps a frozen copy. Anonymous links retain a random /m/<slug> address.
Freeze a link on purpose
Share -> Share Snapshot
A Snapshot keeps the source as shared. Visitors can edit code and click Cast without an account. Sign up to save edits keeps their own copy, including changed inputs. Signed-in visitors choose Save a copy.
Existing Live links keep following their saved source. Share Snapshot freezes new public code shares. Collaborate invites private editors. Share View hides code (Pro); anonymous links expire after 7 days, paid links are permanent.
Excel ExportDownload your model as a formatted .xlsx workbook.
Share -> Export to Excel
Download your model as a formatted .xlsx workbook. Every number is there, and each calculation is a live Excel formula pointing at the cells above it — change an input and the whole sheet recalculates. Inputs are blue, calculations are black, results are boxed. Scenarios, your data grid, forecasts, what-if grids, risk simulations and rule checks each get their own tab, plus one holding the Cast source.
Export a model with scenarios
price = 100 revenue = price * 50 profit = revenue * 0.3 | result @scenario "Bull" price = 150
Model tab with =B7*50 style formulas, plus a Scenarios tab: Base case and Bull side by side
Open the file and click a calculated cell — the formula bar shows the Excel formula, so a spreadsheet person can audit the model without learning Cast. npv is rewritten as cash_flow_today + NPV(rate, later_flows) because Excel's NPV discounts every argument by a period; written that way it lands on Cast's number exactly. The few functions Excel genuinely cannot express (irr, percentile, stock, fetch) export their value and say so in the Notes column, rather than writing a formula that would give a different answer.
Dependency TraceSee what feeds into a variable (upstream) and what it affects (downstream).
Click trace icon on any variable
See what feeds into a variable (upstream) and what it affects (downstream). Shows a DAG (directed acyclic graph) visualization of the full formula chain.
Trace a profit calculation
revenue = 1000 costs = 400 profit = revenue - costs | result
profit depends on: revenue, costs. Nothing depends on profit.
Click the trace icon on any result variable — see its full dependency chain light up.
Version StackSave snapshots of your model, browse history, restore any version, and view line-level diffs showing exactly what changed.
Version button (top bar)
Save snapshots of your model, browse history, restore any version, and view line-level diffs showing exactly what changed. Each model has its own history: New, templates and copied models start separate histories; reload keeps the current model's versions. Draft versions follow their model on its first account save. The toolbar names the version you are on and says "(edited)" as soon as you change something. An unsaved draft keeps its history in this browser; a model saved to your account keeps it beside the model, so everyone working on that model sees each other's versions and who saved each one.
Save a version before making changes
Click version icon -> Save Version -> "Before restructuring"
Snapshot saved. Restore anytime.
Save a version before a big edit — then experiment freely. Restore in one click if it goes wrong.
Scenario CloneOne-click duplicate of any saved scenario.
+ button on scenario card
One-click duplicate of any saved scenario. Creates a copy with "(copy)" appended to the name, ready for editing.
Clone Bull Case to create a variant
Click + on "Bull Case" scenario card
Creates "Bull Case (copy)" with same overrides
Build one strong scenario, then clone it -- tweak the copy instead of rebuilding from scratch.
Variable SearchFilter variables by name in the results pane.
Search box in results pane
Filter variables by name in the results pane. Useful for large models with many variables.
Find all revenue-related variables
Type "revenue" in search box
Shows only variables containing "revenue"
In a big model, type a few letters -- the results pane narrows to just the matching variables.
Table of ContentsSection headers (# NAME) automatically create a sticky navigation bar at the top of the results pane.
# Section headers auto-generate TOC
Section headers (# NAME) automatically create a sticky navigation bar at the top of the results pane. Click any pill to jump to that section.
Auto-generated TOC from sections
# REVENUE price = 100 qty = 50 # COSTS cogs = 200 opex = 100 # PROFIT profit = price * qty - cogs - opex | result
Pill bar: [REVENUE] [COSTS] [PROFIT] — click to jump
Add a # SECTION header above a group of lines -- a clickable jump pill appears in the results nav.
| bridge (Waterfall Chart)Add the | bridge flag to variables that form a value creation chain.
variable = value | bridge
Add the | bridge flag to variables that form a value creation chain. Cast renders a waterfall chart showing how values build from first to last. The first and last bars show totals; middle bars show incremental changes.
EBITDA bridge
revenue = $1M | bridge cogs = $400K | bridge gross = revenue - cogs | bridge opex = $200K | bridge ebitda = gross - opex | bridge | result
Waterfall: Revenue -> -COGS -> Gross -> -OpEx -> EBITDA
Tag each line of a value chain with | bridge -- the waterfall builds from first total to last.
Voice ModeTalk to Cast instead of typing.
Off / Speak / Converse (assistant header)
Talk to Cast instead of typing. Three modes: Off (text only), Speak (Cast reads replies aloud), Converse (mic fills the chat box and auto-sends when you stop talking).
Speak mode — Cast reads every reply aloud
Click the Off / Speak / Converse control -> select Speak
Cast reads its next reply using your device text-to-speech
Converse mode — hands-free Q&A
Select Converse -> tap the mic -> speak your question
Your words appear in the chat box and send automatically when you stop talking
Switch to Converse, tap the mic, and ask Cast out loud -- it sends when you stop talking.
CreditsMonthly AI credits stays visible above chat, with remaining out of total, a bar, percentage remaining and reset date.
Monthly AI credits → remaining / allowance
Monthly AI credits stays visible above chat, with remaining out of total, a bar, percentage remaining and reset date. Each answer states its actual credit cost. AI chat uses model-weighted credits; searches and research calls also use the balance. Free includes 60 credits a month; Pro includes 1,000, and Teams includes 1,000 per seat. Used in this chat counts this conversation.
Check your balance
Open the assistant panel, pick a model, then click Model and usage to minimize it. Click again to show the details. Your selection and layout are kept.
Monthly AI credits shows "720 of 1,000 credits left", "72% remaining" and a reset date, even when minimized. An answer receipt says "This answer used 20 credits".
New chats start with Opus; GPT Sol is second in the model picker. Saved chats keep their model choice. Each model receives its supported output allowance, including reasoning and the visible reply. The context meter shows that allowance. Switching to a cheaper model makes your credits last longer. An administrator-granted account exception shows Unlimited; ordinary plan limits remain unchanged.
| chart on Forecast (Line Chart)Add | chart to forecast formulas to show their trends.
forecast N days|weeks|months|years:\n variable = expr | chart
Add | chart to forecast formulas to show their trends. Lines with matching time and value units combine in one chart by default. The title describes the time span. Each line has a different color and ends with its name and final value; narrow or crowded charts use a legend. Currency labels show whole amounts, while the math keeps cents. Currency and percentage lines, or different time units, keep separate charts so their scales remain clear.
Revenue growth line chart
base = $1000000 forecast 5 years: revenue = base * 1.05^year | chart
5-year forecast with the Revenue line ending at $1,276,282
Monthly growth line chart
base = $100000 forecast 12 months: mrr = base * 1.02^month | chart
Line chart: Month 1 -> Month 12
Combined forecast lines
base = $1000000 margin = 30% forecast 5 years: revenue = base * 1.05^year | chart profit = revenue * margin | chart
One 5-year forecast chart with Revenue and Profit in different colors, each labeled at its final value
Add | chart to a forecast line -- the trend redraws every time you change the growth rate.
AutocompleteAs you type, the editor suggests matches: your own variables first (with their definitions), then Cast functions, @ directives, and the flags after a |.
Type two letters in the editor and pick from the list
As you type, the editor suggests matches: your own variables first (with their definitions), then Cast functions, @ directives, and the flags after a |. Press Enter to accept; a function arrives with its brackets and your cursor inside. Ctrl+Space asks for suggestions after one letter. It stays quiet inside comments, section titles and text. The Autocomplete On/Off button in the editor toolbar switches it, and Cast remembers your choice.
Finish a name you already defined
revenue = 100 profit = rev
The list shows revenue (your variable) first; Enter completes it
Turn it off with the Autocomplete button in the editor toolbar if you prefer typing everything yourself.
Click-to-trace in editorClick a variable name in the editor, or click a result card, a variable row, or a forecast row label, and every place that name is used lights up in the editor.
Click any variable name in the editor, or any result; hover a name for its value
Click a variable name in the editor, or click a result card, a variable row, or a forecast row label, and every place that name is used lights up in the editor. The names it is made from light up in a second color on its defining line, so you can read "X = A * B" at a glance. Hover any name in the editor for a small card with its defining line and current value. Click anywhere else to clear.
See where a number comes from
Click material_payments in the editor
Every material_payments lights up; material_purchases and purchases_lag_1 light up in blue on its line
Works on every name in the file, including rows inside a forecast block.
Stock Data
Pull live stock prices, earnings, revenue, and historical data directly into your model.
stockCurrent stock price in dollars.
stock('SYMBOL')Current stock price in dollars. Fetches data when Cast runs (cached 5 min). Saved stock key awareness lets the assistant use an existing FMP key without asking again. Missing setup shows a data error. The Live Finance Data helper minimizes from its header and remembers your choice. Suggested code does not mean data has already been fetched.
Get current Apple stock price
price = stock('AAPL')173.50 (live)
Swap AAPL for MSFT or TSLA — same formula, live price updates. Try stock('AAPL') / earnings('AAPL') for a P/E ratio.
earningsLatest annual earnings per share (EPS).
earnings('SYMBOL')Latest annual earnings per share (EPS). Fetched from income statement.
Calculate P/E ratio
pe = stock('AAPL') / earnings('AAPL')28.5 (varies with market)
revenueLatest annual revenue.
revenue('SYMBOL')Latest annual revenue. Fetched from income statement.
Revenue per share
rev_per_share = revenue('AAPL') / market_cap('AAPL') * stock('AAPL')Price-to-sales component
net_incomeLatest annual net income (profit after all expenses and taxes).
net_income('SYMBOL')Latest annual net income (profit after all expenses and taxes).
Profit margin
margin = net_income('AAPL') / revenue('AAPL')0.25 (25% margin)
market_capCurrent market capitalization (total value of all shares).
market_cap('SYMBOL')Current market capitalization (total value of all shares).
Compare company sizes
apple = market_cap('AAPL')
msft = market_cap('MSFT')Market cap values for comparison
dividendAnnual dividend per share.
dividend('SYMBOL')Annual dividend per share. Returns 0 if the company does not pay dividends.
Dividend yield
yield = dividend('AAPL') / stock('AAPL')0.005 (0.5% yield)
stock_historyArray of N daily closing prices (most recent last).
stock_history('SYMBOL', days)Array of N daily closing prices (most recent last). Use with stats functions for analysis.
30-day average and volatility
prices = stock_history('AAPL', 30) | hidden
avg_price = avg(prices)
volatility = cv(prices)Average price and coefficient of variation
earnings_historyArray of 5 years of annual earnings per share (oldest first).
earnings_history('SYMBOL')Array of 5 years of annual earnings per share (oldest first). Use with pearson() to test earnings growth trends.
Earnings growth trend
trend = pearson([1,2,3,4,5], earnings_history('AAPL')){ r: 0.95, significant: true } (strong uptrend)
revenue_historyArray of 5 years of annual revenue (oldest first).
revenue_history('SYMBOL')Array of 5 years of annual revenue (oldest first). Use for trend analysis.
Revenue growth rate
revs = revenue_history('AAPL') | hidden
growth = revs[4] / revs[0] - 10.45 (45% growth over 5 years)
fetchFetch data from a custom API connector.
fetch('connector_name') or fetch('connector_name', 'param')Fetch data from a custom API connector. Set up connectors in Settings > Live Data, or ask Cast to propose one.
Fetch Bitcoin price from a custom connector
btc = fetch('bitcoin_price')63500 (current price from connector)
Fetch a specific cryptocurrency price
eth = fetch('crypto_price', 'ethereum')3200 (Ethereum price from connector)
Accounting
Pull invoices, transactions, payments, and contacts from your connected accounting software.
invoicesPull invoice data from connected accounting software (QuickBooks, Xero, FreshBooks).
invoices('status', 'field')Pull invoice data from connected accounting software (QuickBooks, Xero, FreshBooks). Returns an array of field values filtered by status. Connect in Settings > Accounting.
Total accounts receivable
accounts_receivable = sum(invoices('outstanding', 'total_amount')) | result$10,000 (sum of outstanding invoice amounts)
Count overdue invoices
overdue_invoices = count(invoices('overdue', 'total_amount')) | result3 (number of overdue invoices)
Change 'outstanding' to 'overdue' — see late invoices only. Change 'total_amount' to 'balance_amount' for what is still owed.
contactsPull contact data from connected accounting software.
contacts('type', 'field')Pull contact data from connected accounting software. Returns an array of field values filtered by type.
List customer names
customers = contacts('customer', 'name')["Acme Corp", "Widget Inc", ...]
transactionsPull transaction data from connected accounting software.
transactions('type', 'field')Pull transaction data from connected accounting software. Returns an array of field values filtered by type.
Total expenses
total_expenses = sum(transactions('expense', 'total_amount')) | result$45,000
Change 'expense' to 'revenue' — flip to income view. Wrap in avg() for the average transaction size.
paymentsPull payment data from connected accounting software.
payments('period', 'field')Pull payment data from connected accounting software. Returns an array of field values filtered by period.
Payments received this year
payments_ytd = sum(payments('2026', 'amount')) | result$120,000
Change '2026' to '2026-Q1' for a quarterly view. Try count() instead of sum() for number of payments.
accountsPull chart of accounts data from connected accounting software.
accounts('type', 'field')Pull chart of accounts data from connected accounting software. Returns an array of field values filtered by type.
Asset account names
assets = accounts('asset', 'name')["Cash", "Accounts Receivable", ...]