Skip to content

Coppock Curve is added.#331

Merged
cinar merged 1 commit intomasterfrom
feature/coppock-curve
Feb 26, 2026
Merged

Coppock Curve is added.#331
cinar merged 1 commit intomasterfrom
feature/coppock-curve

Conversation

@cinar
Copy link
Owner

@cinar cinar commented Feb 26, 2026

Describe Request

Coppock Curve is added.

Change Type

New indicator.

Summary by CodeRabbit

  • New Features

    • Added Coppock Curve momentum indicator for technical analysis. The indicator combines dual rate-of-change calculations with weighted moving average smoothing. Supports both default configuration and custom period parameters.
  • Tests

    • Added comprehensive test coverage including validation of default behavior, custom parameter configurations, and accuracy of computed values.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

Introduces a new Coppock Curve momentum indicator to the momentum package. Adds a generic CoppockCurve[T] type with configurable ROC and WMA periods, implements the computation formula WMA(ROC(14) + ROC(11), 10), and includes constructors, validation, and comprehensive test coverage with testdata validation.

Changes

Cohort / File(s) Summary
Documentation
README.md, momentum/README.md
Added Coppock Curve to momentum indicators list and generated documentation for the new public API surface including type, constructors, methods, and default constants.
Implementation
momentum/coppock_curve.go
New file implementing the Coppock Curve oscillator with generic type CoppockCurve[T], default constants (RocPeriod1=14, RocPeriod2=11, WmaPeriod=10), constructors with validation, and Compute method that chains ROC calculations, sums results, and applies WMA.
Tests
momentum/coppock_curve_test.go
Comprehensive test suite including basic constructor/IdlePeriod verification and data-driven testdata tests that validate Compute output against expected values from CSV reference data.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant CoppockCurve as CoppockCurve.Compute
    participant ROC1 as ROC(Period1)
    participant ROC2 as ROC(Period2)
    participant Sum as Summation & Scaling
    participant WMA as WMA(Period)
    participant Output as Output Channel

    Caller->>CoppockCurve: values channel
    CoppockCurve->>ROC1: Start ROC(14) stream
    CoppockCurve->>ROC2: Start ROC(11) stream
    ROC1-->>CoppockCurve: ROC1 values
    ROC2-->>CoppockCurve: ROC2 values
    CoppockCurve->>Sum: Align ROC streams
    Sum->>Sum: Scale by 100 & sum
    Sum->>WMA: Summed values
    WMA-->>CoppockCurve: WMA(10) results
    CoppockCurve-->>Output: Final stream
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

  • Add momentum indicator ROC #277: The CoppockCurve implementation directly depends on ROC streams (ROC(14) and ROC(11)) that correspond to the ROC indicator; understanding that related PR provides context for the internal computation pipeline.

Poem

🐰 A new curve hops into view,
ROC rates dance, then WMA too,
Momentum flows through channel streams,
Coppock dreams in fuzzy beams! 📊✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change—adding a new Coppock Curve indicator to the codebase.
Description check ✅ Passed The description matches the required template structure with both sections completed, though the content is minimal and lacks implementation details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/coppock-curve

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.19%. Comparing base (f59bab6) to head (b7e6afc).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #331      +/-   ##
==========================================
+ Coverage   93.14%   93.19%   +0.04%     
==========================================
  Files         208      209       +1     
  Lines        5849     5889      +40     
==========================================
+ Hits         5448     5488      +40     
  Misses        321      321              
  Partials       80       80              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@momentum/coppock_curve.go`:
- Around line 25-35: Update the doc comment above the CoppockCurve type to use
explicit fenced-code languages: wrap the formula line with a plain text fence
and the example with a ```go fenced block (i.e., replace the unlabeled
triple-backtick blocks with "```text" for the formula and "```go" for the
example), ensure the example code lines begin with // inside the comment and
close the fences, then regenerate the README/docs so the labeled fences appear
in momentum/README.md; locate the comment immediately preceding the
CoppockCurve[T helper.Float] struct to make the edits.
- Around line 74-113: Compute and IdlePeriod currently read the raw public
fields (RocPeriod1, RocPeriod2, WmaPeriod) which can be zero/invalid if the
struct is zero-valued or mutated; normalize these periods at the start of both
Compute and IdlePeriod (e.g., derive local maxRocPeriod, roc1Period, roc2Period,
wmaPeriod from the fields but applying the same defaulting/validation logic used
in NewCoppockCurveWithPeriods) so all downstream calls (helper.Buffered,
trend.NewRocWithPeriod, trend.NewWmaWith, helper.Skip and the IdlePeriod
formula) use validated positive periods; reference the symbols RocPeriod1,
RocPeriod2, WmaPeriod, Compute, IdlePeriod, and NewCoppockCurveWithPeriods when
implementing the single normalization path.
- Line 35: The generic constraint on CoppockCurve is too narrow — change the
type parameter from helper.Float to helper.Number so the indicator accepts both
Integer and Float inputs; update the struct declaration for CoppockCurve[T
helper.Number] and similarly replace helper.Float with helper.Number in the
other affected indicators (Rvi, Fisher, ConnorsRsi, PringsSpecialK) to keep the
package consistent.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f59bab6 and b7e6afc.

⛔ Files ignored due to path filters (1)
  • momentum/testdata/coppock_curve.csv is excluded by !**/*.csv
📒 Files selected for processing (4)
  • README.md
  • momentum/README.md
  • momentum/coppock_curve.go
  • momentum/coppock_curve_test.go

Comment on lines +25 to +35
// CoppockCurve represents the configuration parameters for calculating the
// Coppock Curve oscillator. The Coppock Curve is a momentum indicator
// used to identify long-term buying opportunities in equity indices.
//
// Coppock Curve = WMA(ROC(14) + ROC(11), 10)
//
// Example:
//
// cc := momentum.NewCoppockCurve[float64]()
// result := cc.Compute(closings)
type CoppockCurve[T helper.Float] struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use explicit code-fence languages in the doc comment.

The current comment formatting generates unlabeled fenced blocks in momentum/README.md (markdownlint MD040). Add explicit text/go fences in the source comment, then regenerate docs.

📝 Suggested doc-comment adjustment
 // CoppockCurve represents the configuration parameters for calculating the
 // Coppock Curve oscillator. The Coppock Curve is a momentum indicator
 // used to identify long-term buying opportunities in equity indices.
 //
-//	Coppock Curve = WMA(ROC(14) + ROC(11), 10)
+// ```text
+// Coppock Curve = WMA(ROC(14) + ROC(11), 10)
+// ```
 //
 // Example:
 //
-//	cc := momentum.NewCoppockCurve[float64]()
-//
-//	result := cc.Compute(closings)
+// ```go
+// cc := momentum.NewCoppockCurve[float64]()
+// result := cc.Compute(closings)
+// ```
 type CoppockCurve[T helper.Float] struct {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// CoppockCurve represents the configuration parameters for calculating the
// Coppock Curve oscillator. The Coppock Curve is a momentum indicator
// used to identify long-term buying opportunities in equity indices.
//
// Coppock Curve = WMA(ROC(14) + ROC(11), 10)
//
// Example:
//
// cc := momentum.NewCoppockCurve[float64]()
// result := cc.Compute(closings)
type CoppockCurve[T helper.Float] struct {
// CoppockCurve represents the configuration parameters for calculating the
// Coppock Curve oscillator. The Coppock Curve is a momentum indicator
// used to identify long-term buying opportunities in equity indices.
//
//
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@momentum/coppock_curve.go` around lines 25 - 35, Update the doc comment above
the CoppockCurve type to use explicit fenced-code languages: wrap the formula
line with a plain text fence and the example with a ```go fenced block (i.e.,
replace the unlabeled triple-backtick blocks with "```text" for the formula and
"```go" for the example), ensure the example code lines begin with // inside the
comment and close the fences, then regenerate the README/docs so the labeled
fences appear in momentum/README.md; locate the comment immediately preceding
the CoppockCurve[T helper.Float] struct to make the edits.

//
// cc := momentum.NewCoppockCurve[float64]()
// result := cc.Compute(closings)
type CoppockCurve[T helper.Float] struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Momentum indicators and their type constraints ==="
rg -n --type go 'type\s+\w+\[T helper\.(Number|Float|int|float|decimal)\]' momentum/ -g '!**/*_test.go' 2>/dev/null || true

echo ""
echo "=== Helper package type definitions ==="
rg -n --type go '(type Number|type Float)' helper/ 2>/dev/null || true

echo ""
echo "=== CoppockCurve struct definition ==="
rg -n --type go -A 5 'type CoppockCurve\[' momentum/coppock_curve.go

echo ""
echo "=== ROC definition in trend package (if exists) ==="
if [ -f "trend/roc.go" ]; then
  rg -n --type go 'type Roc\[' trend/roc.go -A 3
else
  echo "trend/roc.go not found"
fi

Repository: cinar/indicator

Length of output: 1812


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Float and Number interface definitions ==="
rg -n --type go -A 5 'type (Float|Number) interface' helper/helper.go

Repository: cinar/indicator

Length of output: 255


Change generic constraint from helper.Float to helper.Number.

The momentum guideline requires all indicators to be generic over helper.Number, but this uses helper.Float. Since Number = Integer | Float, using Float artificially restricts valid input types. This pattern appears in multiple momentum indicators (Rvi, Fisher, ConnorsRsi, PringsSpecialK) and should be corrected across the package for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@momentum/coppock_curve.go` at line 35, The generic constraint on CoppockCurve
is too narrow — change the type parameter from helper.Float to helper.Number so
the indicator accepts both Integer and Float inputs; update the struct
declaration for CoppockCurve[T helper.Number] and similarly replace helper.Float
with helper.Number in the other affected indicators (Rvi, Fisher, ConnorsRsi,
PringsSpecialK) to keep the package consistent.

Comment on lines +74 to +113
func (c *CoppockCurve[T]) Compute(values <-chan T) <-chan T {
maxRocPeriod := c.RocPeriod1
if c.RocPeriod2 > maxRocPeriod {
maxRocPeriod = c.RocPeriod2
}

values = helper.Buffered(values, maxRocPeriod)
valuesSplice := helper.Duplicate(values, 2)

roc1 := trend.NewRocWithPeriod[T](c.RocPeriod1)
roc1Values := helper.MultiplyBy(roc1.Compute(valuesSplice[0]), 100)

roc2 := trend.NewRocWithPeriod[T](c.RocPeriod2)
roc2Values := helper.MultiplyBy(roc2.Compute(valuesSplice[1]), 100)

// Align ROC streams. Both Compute calls return streams that have already skipped their own IdlePeriod.
// To align them to maxRocPeriod, we skip the remaining difference.
if c.RocPeriod1 < maxRocPeriod {
roc1Values = helper.Skip(roc1Values, maxRocPeriod-c.RocPeriod1)
}
if c.RocPeriod2 < maxRocPeriod {
roc2Values = helper.Skip(roc2Values, maxRocPeriod-c.RocPeriod2)
}

sumRocs := helper.Add(roc1Values, roc2Values)

wma := trend.NewWmaWith[T](c.WmaPeriod)
return wma.Compute(sumRocs)
}

// IdlePeriod is the initial period that Coppock Curve won't yield any results.
func (c *CoppockCurve[T]) IdlePeriod() int {
maxRocPeriod := c.RocPeriod1
if c.RocPeriod2 > maxRocPeriod {
maxRocPeriod = c.RocPeriod2
}

// maxRocPeriod (from ROC) + (WmaPeriod - 1) (from WMA)
return maxRocPeriod + c.WmaPeriod - 1
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Normalize periods inside Compute and IdlePeriod, not only in constructors.

NewCoppockCurveWithPeriods applies defaults, but Compute/IdlePeriod read raw fields directly. Since fields are public, zero/negative values via zero-value struct or later mutation can produce wrong stream alignment and runtime failures.

🛠️ Suggested fix (single normalization path)
+func (c *CoppockCurve[T]) normalizedPeriods() (rocPeriod1, rocPeriod2, wmaPeriod int) {
+	rocPeriod1 = c.RocPeriod1
+	if rocPeriod1 <= 0 {
+		rocPeriod1 = DefaultCoppockCurveRocPeriod1
+	}
+
+	rocPeriod2 = c.RocPeriod2
+	if rocPeriod2 <= 0 {
+		rocPeriod2 = DefaultCoppockCurveRocPeriod2
+	}
+
+	wmaPeriod = c.WmaPeriod
+	if wmaPeriod <= 0 {
+		wmaPeriod = DefaultCoppockCurveWmaPeriod
+	}
+
+	return
+}
+
 // Compute function takes a channel of closings and computes the Coppock Curve.
 func (c *CoppockCurve[T]) Compute(values <-chan T) <-chan T {
-	maxRocPeriod := c.RocPeriod1
-	if c.RocPeriod2 > maxRocPeriod {
-		maxRocPeriod = c.RocPeriod2
+	rocPeriod1, rocPeriod2, wmaPeriod := c.normalizedPeriods()
+
+	maxRocPeriod := rocPeriod1
+	if rocPeriod2 > maxRocPeriod {
+		maxRocPeriod = rocPeriod2
 	}
 
 	values = helper.Buffered(values, maxRocPeriod)
 	valuesSplice := helper.Duplicate(values, 2)
 
-	roc1 := trend.NewRocWithPeriod[T](c.RocPeriod1)
+	roc1 := trend.NewRocWithPeriod[T](rocPeriod1)
 	roc1Values := helper.MultiplyBy(roc1.Compute(valuesSplice[0]), 100)
 
-	roc2 := trend.NewRocWithPeriod[T](c.RocPeriod2)
+	roc2 := trend.NewRocWithPeriod[T](rocPeriod2)
 	roc2Values := helper.MultiplyBy(roc2.Compute(valuesSplice[1]), 100)
 
 	// Align ROC streams. Both Compute calls return streams that have already skipped their own IdlePeriod.
 	// To align them to maxRocPeriod, we skip the remaining difference.
-	if c.RocPeriod1 < maxRocPeriod {
-		roc1Values = helper.Skip(roc1Values, maxRocPeriod-c.RocPeriod1)
+	if rocPeriod1 < maxRocPeriod {
+		roc1Values = helper.Skip(roc1Values, maxRocPeriod-rocPeriod1)
 	}
-	if c.RocPeriod2 < maxRocPeriod {
-		roc2Values = helper.Skip(roc2Values, maxRocPeriod-c.RocPeriod2)
+	if rocPeriod2 < maxRocPeriod {
+		roc2Values = helper.Skip(roc2Values, maxRocPeriod-rocPeriod2)
 	}
 
 	sumRocs := helper.Add(roc1Values, roc2Values)
 
-	wma := trend.NewWmaWith[T](c.WmaPeriod)
+	wma := trend.NewWmaWith[T](wmaPeriod)
 	return wma.Compute(sumRocs)
 }
 
 // IdlePeriod is the initial period that Coppock Curve won't yield any results.
 func (c *CoppockCurve[T]) IdlePeriod() int {
-	maxRocPeriod := c.RocPeriod1
-	if c.RocPeriod2 > maxRocPeriod {
-		maxRocPeriod = c.RocPeriod2
+	rocPeriod1, rocPeriod2, wmaPeriod := c.normalizedPeriods()
+	maxRocPeriod := rocPeriod1
+	if rocPeriod2 > maxRocPeriod {
+		maxRocPeriod = rocPeriod2
 	}
 
 	// maxRocPeriod (from ROC) + (WmaPeriod - 1) (from WMA)
-	return maxRocPeriod + c.WmaPeriod - 1
+	return maxRocPeriod + wmaPeriod - 1
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@momentum/coppock_curve.go` around lines 74 - 113, Compute and IdlePeriod
currently read the raw public fields (RocPeriod1, RocPeriod2, WmaPeriod) which
can be zero/invalid if the struct is zero-valued or mutated; normalize these
periods at the start of both Compute and IdlePeriod (e.g., derive local
maxRocPeriod, roc1Period, roc2Period, wmaPeriod from the fields but applying the
same defaulting/validation logic used in NewCoppockCurveWithPeriods) so all
downstream calls (helper.Buffered, trend.NewRocWithPeriod, trend.NewWmaWith,
helper.Skip and the IdlePeriod formula) use validated positive periods;
reference the symbols RocPeriod1, RocPeriod2, WmaPeriod, Compute, IdlePeriod,
and NewCoppockCurveWithPeriods when implementing the single normalization path.

@cinar cinar merged commit d7456c4 into master Feb 26, 2026
6 checks passed
@cinar cinar deleted the feature/coppock-curve branch February 26, 2026 04:46
@github-actions github-actions bot locked and limited conversation to collaborators Feb 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants