JSA Moving Average (JSA)
period = 30 Overview
The JSA Moving Average was introduced by George R. Arrington, Ph.D. in Technical Analysis of Stocks & Commodities as an extremely simple formula with many uses. On each bar it computes
a value equal to half the sum of the current price and the price from period bars earlier, so the line tends to run below price during sustained uptrends and above price during downtrends
instead of hugging every small fluctuation. Many traders treat the JSA line as both a dynamic support/resistance level and a wide-berth trailing stop: as long as price stays clearly on the trend side
of the JSA, the prevailing move is usually considered intact, while frequent crossings or price riding directly on the line often signal a choppy or range-bound environment. Because it uses only two
price points per bar, JSA is computationally cheap and its effective lag is easy to reason about (roughly half the chosen period), which makes it straightforward to integrate into systematic strategies
or real-time charting workflows.
Implementation Examples
Get started with JSA in just a few lines:
use vectorta::indicators::jsa::{jsa, JsaInput, JsaParams};
use vectorta::utilities::data_loader::{Candles, read_candles_from_csv};
// Using with a price slice
let prices = vec![100.0, 101.0, 102.0, 101.5, 103.0, 104.0];
let params = JsaParams { period: Some(30) };
let input = JsaInput::from_slice(&prices, params);
let result = jsa(&input)?;
// Using with Candles (defaults: period=30, source="close")
let candles: Candles = read_candles_from_csv("data/sample.csv")?;
let input = JsaInput::with_default_candles(&candles);
let result = jsa(&input)?;
// Access values
for value in result.values {
println!("JSA: {}", value);
} API Reference
Input Methods ▼
// From price slice
JsaInput::from_slice(&[f64], JsaParams) -> JsaInput
// From candles with custom source
JsaInput::from_candles(&Candles, &str, JsaParams) -> JsaInput
// From candles with defaults (source="close", period=30)
JsaInput::with_default_candles(&Candles) -> JsaInput Parameters Structure ▼
pub struct JsaParams {
pub period: Option<usize>, // Default: 30
} Output Structure ▼
pub struct JsaOutput {
pub values: Vec<f64>, // JSA[i] = 0.5 * (x[i] + x[i - period]) after warmup; leading values are NaN
} Validation, Warmup & NaNs ▼
period > 0andperiod ≤ data.len(); elseJsaError::InvalidPeriod.- Requires at least
periodvalid points after the first finite value; elseJsaError::NotEnoughValidData. - Indices before
first_valid + periodareNaN; from there:out[i] = (x[i] + x[i - period]) * 0.5. - Empty input →
JsaError::EmptyInputData; all-NaN input →JsaError::AllValuesNaN. - Into-APIs validate output length; mismatch →
JsaError::OutputLenMismatch. - Streaming: returns
Noneuntilperiodsamples are seen;NaNinputs propagate.
Error Handling ▼
use vectorta::indicators::jsa::{jsa, JsaError, JsaInput, JsaParams};
let input = JsaInput::from_slice(&prices, JsaParams { period: Some(30) });
match jsa(&input) {
Ok(out) => println!("{} values", out.values.len()),
Err(JsaError::EmptyInputData) => eprintln!("input slice is empty"),
Err(JsaError::AllValuesNaN) => eprintln!("all inputs are NaN"),
Err(JsaError::InvalidPeriod { period, data_len }) => {
eprintln!("invalid period {period} for length {data_len}")
}
Err(JsaError::NotEnoughValidData { needed, valid }) => {
eprintln!("need {needed} valid points, got {valid}")
}
Err(JsaError::OutputLenMismatch { expected, got }) => {
eprintln!("dst len {got}, expected {expected}")
}
Err(JsaError::InvalidKernel { kernel }) => eprintln!("invalid batch kernel: {kernel:?}"),
} Python Bindings
Basic Usage ▼
Calculate JSA using NumPy arrays (default period=30):
import numpy as np
from vectorta import jsa
prices = np.array([100.0, 101.0, 102.0, 101.5, 103.0, 104.0])
# Default period (30) or specify explicitly
out = jsa(prices, period=30)
# Specify kernel ("auto", "scalar", "avx2", "avx512")
out = jsa(prices, period=30, kernel="auto")
print(out) # NumPy array, same length as input Streaming Real-time Updates ▼
Process real-time price updates efficiently:
from vectorta import JsaStream
stream = JsaStream(period=30)
for price in price_feed:
value = stream.update(price)
if value is not None:
print(value) Batch Parameter Optimization ▼
Test multiple periods efficiently:
import numpy as np
from vectorta import jsa_batch
prices = np.array([...])
results = jsa_batch(
prices,
period_start=10, period_end=50, period_step=10,
kernel="auto"
)
# results is a dict with 'values' (2D), 'periods', 'rows', 'cols'
print(results['values'].shape)
print(results['periods']) CUDA Acceleration ▼
CUDA support for JSA is available as developer APIs; final interfaces may change.
# Developer APIs (subject to change)
from vectorta import jsa_cuda_batch_dev, jsa_cuda_many_series_one_param_dev
import numpy as np
# One series, many parameters
prices_f32 = np.array([...], dtype=np.float32)
dev_arr = jsa_cuda_batch_dev(prices_f32, period_range=(10, 50, 10), device_id=0)
# Many series, one parameter (time-major [T, N])
tm = np.array([...], dtype=np.float32) # shape: [T, N]
dev_arr2 = jsa_cuda_many_series_one_param_dev(tm, period=30, device_id=0) JavaScript/WASM Bindings
Basic Usage ▼
Calculate JSA in JavaScript/TypeScript:
import { jsa_js } from 'vectorta-wasm';
const prices = new Float64Array([100, 101, 102, 101.5, 103, 104]);
const out = jsa_js(prices, 30); // period=30
console.log('JSA:', out); Memory-Efficient Operations ▼
Use zero-copy operations for large datasets:
import { jsa_alloc, jsa_free, jsa_into, memory } from 'vectorta-wasm';
const prices = new Float64Array([/* your data */]);
const n = prices.length;
const inPtr = jsa_alloc(n);
const outPtr = jsa_alloc(n);
new Float64Array(memory.buffer, inPtr, n).set(prices);
// Compute directly into pre-allocated memory
jsa_into(inPtr, outPtr, n, 30);
const jsaValues = new Float64Array(memory.buffer, outPtr, n).slice();
jsa_free(inPtr, n);
jsa_free(outPtr, n);
console.log(jsaValues); Batch Processing ▼
Test multiple periods efficiently:
import { jsa_batch_js } from 'vectorta-wasm';
const prices = new Float64Array([/* historical prices */]);
const config = { period_range: [10, 50, 10] }; // start, end, step
const { values, periods, rows, cols } = jsa_batch_js(prices, config) as unknown as {
values: Float64Array; periods: number[]; rows: number; cols: number;
};
// Reshape flat results into matrix rows x cols
const matrix: Float64Array[] = [];
for (let r = 0; r < rows; r++) {
const start = r * cols;
matrix.push(values.slice(start, start + cols));
}
console.log('Periods tested:', periods); Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05
Related Indicators
Arnaud Legoux Moving Average
Moving average indicator
Compound Ratio Moving Average (CoRa Wave)
Moving average indicator
Centered Weighted Moving Average
Moving average indicator
Double Exponential Moving Average
Moving average indicator
Ehlers Distance Coefficient Filter
Moving average indicator
Ehlers Error-Correcting EMA (ECEMA)
Moving average indicator