Coppock Curve

Parameters: 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 vectorta::indicators::coppock::{coppock, CoppockInput, CoppockParams};
use vectorta::utilities::data_loader::Candles;

let closes = vec![3980.0, 4015.2, 4072.9, 3998.4, 3927.3, 3891.6, 3964.8];
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 vectorta::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 vectorta::indicators::coppock::{coppock_into_slice, CoppockInput, CoppockParams};
use vectorta::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 vectorta import coppock

prices = np.array([3980.0, 4015.2, 4072.9, 3998.4, 3927.3, 3891.6, 3964.8], 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 vectorta import CoppockStream

stream = CoppockStream(short_roc_period=11, long_roc_period=14, ma_period=10, ma_type="wma")
prices = np.array([3980.0, 4015.2, 4072.9, 3998.4, 3927.3, 3891.6, 3964.8, 4018.2], 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 vectorta import coppock_batch

prices = np.array([3980.0, 4015.2, 4072.9, 3998.4, 3927.3, 3891.6, 3964.8, 4018.2, 4087.1], 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 bindings for the Coppock Curve follow the same API shape as other GPU-enabled indicators and are currently under development.

# Coming soon: CUDA-accelerated Coppock Curve routines
#
# from vectorta import coppock_cuda_batch, coppock_cuda_many_series_one_param
# import numpy as np
#
# prices = np.array([...], dtype=np.float32)
# gpu_results = coppock_cuda_batch(
#     data=prices,
#     short_range=(8, 20, 1),
#     long_range=(16, 32, 2),
#     ma_range=(8, 16, 1),
#     device_id=0,
# )
#
# grid = np.array([...], dtype=np.float32)  # Shape: [time, assets]
# portfolio = coppock_cuda_many_series_one_param(
#     data_tm_f32=grid,
#     short_roc_period=11,
#     long_roc_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([3980.0, 4015.2, 4072.9, 3998.4, 3927.3, 3891.6, 3964.8]);
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));

Performance Analysis

Comparison:
View:
Loading chart...

AMD Ryzen 9 9950X (CPU) | NVIDIA RTX 4090 (GPU) | Benchmarks: 2026-01-05

Related Indicators