Coppock Curve
short_roc_period = 11 (1–120) | long_roc_period = 14 (1–240) | ma_period = 10 (1–240) | ma_type = wma Overview
The Coppock Curve, created by Edwin Coppock in 1962, identifies major buying opportunities by measuring the recovery from market bottoms. Originally designed for monthly charts of equity indices, the indicator sums two rate of change calculations with different periods, then smooths the result with a weighted moving average. Coppock developed this after observing that market recovery patterns resembled the mourning process, taking 11 to 14 months for investors to recover from losses. The formula combines an 11 month and 14 month rate of change, smoothed by a 10 period weighted moving average.
The indicator oscillates above and below zero, with upturns from deeply negative territory signaling potential major bottoms. Traditional interpretation focuses on when the curve turns up from below zero, marking the transition from bear market despair to early recovery. Monthly readings below negative 200 often coincide with significant market lows, while the first upturn after such extremes frequently marks excellent long term entry points. The smoothing process filters out minor fluctuations, ensuring signals reflect genuine momentum shifts rather than noise.
While originally designed for monthly index analysis, traders have adapted the Coppock Curve to shorter timeframes and individual securities. The indicator works particularly well for identifying cyclical bottoms in trending markets but generates fewer reliable signals during extended sideways periods. Many investors combine Coppock signals with breadth indicators or sentiment measures to confirm major turning points. The curve excels at catching the early stages of new bull markets, though it provides limited guidance for exit timing since it was specifically designed to identify buying opportunities.
Implementation Examples
Compute the curve from price slices or candles in a few lines:
use vector_ta::indicators::coppock::{coppock, CoppockInput, CoppockParams};
use vector_ta::utilities::data_loader::Candles;
let closes: Vec<f64> = load_prices()?; // needs > max(short,long) + ma_period points for finite output
let params = CoppockParams {
short_roc_period: Some(11),
long_roc_period: Some(14),
ma_period: Some(10),
ma_type: Some("wma".into()),
};
let input = CoppockInput::from_slice(&closes, params);
let output = coppock(&input)?;
for value in &output.values {
println!("Coppock: {:.4}", value);
}
// Convenience helper: defaults to close prices and classic parameters
let candles: Candles = load_candles()?;
let input = CoppockInput::with_default_candles(&candles);
let output = coppock(&input)?; API Reference
Input Methods ▼
// From raw price slices
CoppockInput::from_slice(&[f64], CoppockParams) -> CoppockInput
// From candles with explicit source column
CoppockInput::from_candles(&Candles, &str, CoppockParams) -> CoppockInput
// Convenience helper (close price + classic defaults)
CoppockInput::with_default_candles(&Candles) -> CoppockInput Parameters Structure ▼
#[derive(Debug, Clone, Default)]
pub struct CoppockParams {
pub short_roc_period: Option<usize>, // Default: 11
pub long_roc_period: Option<usize>, // Default: 14
pub ma_period: Option<usize>, // Default: 10
pub ma_type: Option<String>, // Default: "wma"
} Output Structures ▼
pub struct CoppockOutput {
pub values: Vec<f64>, // NaNs until ROC + MA warm-up completes
}
pub struct CoppockBatchOutput {
pub values: Vec<f64>, // Flattened [rows * cols]
pub combos: Vec<CoppockParams>,
pub rows: usize,
pub cols: usize,
}
impl CoppockBatchOutput {
pub fn row_for_params(&self, p: &CoppockParams) -> Option<usize> { /* ... */ }
pub fn values_for(&self, p: &CoppockParams) -> Option<&[f64]> { /* ... */ }
} Error Handling ▼
use vector_ta::indicators::coppock::{coppock, CoppockError};
match coppock(&input) {
Ok(output) => process(output.values),
Err(CoppockError::EmptyData) => eprintln!("coppock: empty data provided"),
Err(CoppockError::AllValuesNaN) => eprintln!("coppock: all values are NaN"),
Err(CoppockError::NotEnoughValidData { needed, valid }) => {
eprintln!("coppock: need {needed} valid points, only {valid} present");
}
Err(CoppockError::InvalidPeriod { short, long, ma, data_len }) => {
eprintln!("coppock: periods short={short}, long={long}, ma={ma} invalid for len={data_len}");
}
Err(CoppockError::MaError(err)) => eprintln!("coppock: MA failure -> {err}"),
} SIMD & Zero-Copy Helpers ▼
use vector_ta::indicators::coppock::{coppock_into_slice, CoppockInput, CoppockParams};
use vector_ta::utilities::enums::Kernel;
let params = CoppockParams::default();
let input = CoppockInput::from_slice(&closes, params);
let mut buffer = vec![f64::NAN; closes.len()];
coppock_into_slice(&mut buffer, &input, Kernel::Auto)?;
assert_eq!(buffer.len(), closes.len()); Python Bindings
Basic Usage ▼
Classic defaults with optional kernel selection:
import numpy as np
from vector_ta import coppock
prices = np.asarray(load_prices(), dtype=np.float64)
values = coppock(
prices,
short_roc_period=11,
long_roc_period=14,
ma_period=10,
)
alt_values = coppock(
prices,
short_roc_period=10,
long_roc_period=20,
ma_period=12,
ma_type="ema",
kernel="avx2",
)
print(values[-3:])
print(alt_values[-3:]) Streaming Updates ▼
Track the curve in real time with the bound CoppockStream:
import numpy as np
from vector_ta import CoppockStream
stream = CoppockStream(short_roc_period=11, long_roc_period=14, ma_period=10, ma_type="wma")
prices = np.asarray(load_prices(), dtype=np.float64)
for price in prices:
value = stream.update(float(price))
if value is None:
print("warming up...")
else:
print(f"coppock stream: {value:.4f}") Batch Optimization ▼
Evaluate multiple ROC/MA combinations without manual loops:
import numpy as np
from vector_ta import coppock_batch
prices = np.asarray(load_prices(), dtype=np.float64)
result = coppock_batch(
prices,
short_range=(8, 14, 2),
long_range=(16, 26, 2),
ma_range=(8, 12, 1),
kernel="auto",
)
values = result["values"] # Shape: (rows, len(prices))
shorts = result["shorts"]
longs = result["longs"]
ma_periods = result["ma_periods"]
ma_types = result["ma_types"]
best_idx = values[:, -1].argmax()
print(f"Best combo -> short={shorts[best_idx]}, long={longs[best_idx]}, ma={ma_periods[best_idx]}, type={ma_types[best_idx]}") CUDA Acceleration ▼
CUDA helpers are available when the Python package is built with CUDA support. Inputs are accepted as float64 and converted to float32 internally; outputs are device arrays (DLPack / __cuda_array_interface__ compatible). Rust CUDA wrappers are documented below.
import numpy as np
from vector_ta import coppock_cuda_batch_dev, coppock_cuda_many_series_one_param_dev
prices = np.asarray(load_prices(), dtype=np.float64)
dev = coppock_cuda_batch_dev(
prices,
short_range=(8, 14, 2),
long_range=(16, 26, 2),
ma_range=(8, 12, 1),
device_id=0,
)
tm = np.asarray(load_time_major_matrix(), dtype=np.float64) # shape: (rows, cols)
dev_tm = coppock_cuda_many_series_one_param_dev(
tm.ravel(),
cols=tm.shape[1],
rows=tm.shape[0],
short_period=11,
long_period=14,
ma_period=10,
device_id=0,
) JavaScript/WASM Bindings
Basic Usage ▼
Call the WASM export with classic parameters:
import { coppock_js } from 'vectorta-wasm';
const prices = new Float64Array(loadPrices()); // needs > max(short,long) + ma_period points for finite output
const values = coppock_js(prices, 11, 14, 10, 'wma');
console.log('coppock values', values);
console.log('latest', values[values.length - 1]); Zero-Copy & Memory Helpers ▼
Reuse WASM buffers for high-frequency updates:
import { coppock_alloc, coppock_free, coppock_into, memory } from 'vectorta-wasm';
const prices = new Float64Array([/* streaming data */]);
const length = prices.length;
const inputPtr = coppock_alloc(length);
new Float64Array(memory.buffer, inputPtr, length).set(prices);
const outputPtr = coppock_alloc(length);
coppock_into(inputPtr, outputPtr, length, 11, 14, 10, 'wma');
const curve = new Float64Array(memory.buffer, outputPtr, length).slice();
coppock_free(inputPtr, length);
coppock_free(outputPtr, length); Batch Sweeps ▼
Use the config object to enumerate ROC/MA combinations:
import { coppock_batch } from 'vectorta-wasm';
const prices = new Float64Array([3980.0, 4015.2, 4072.9, 3998.4, 3927.3, 3891.6, 3964.8, 4018.2]);
const config = {
short_range: [8, 14, 2],
long_range: [16, 26, 2],
ma_range: [8, 12, 1],
ma_type: 'wma',
};
const { values, combos, rows, cols } = coppock_batch(prices, config);
const firstSeries = values.slice(0, cols);
console.log('first combo', combos[0]);
console.log('series sample', firstSeries.slice(-5)); CUDA Bindings (Rust)
use vector_ta::cuda::CudaCoppock;
use vector_ta::indicators::coppock::CoppockBatchRange;
let cuda = CudaCoppock::new(0)?;
let price: [f32] = /* ... */;
let sweep = CoppockBatchRange::default();
let out = cuda.coppock_batch_dev(&price, &sweep)?;
let _ = out; Performance Analysis
AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-02-28