Skip to content

Commit e9a661d

Browse files
committed
CCI
1 parent 61c1067 commit e9a661d

6 files changed

Lines changed: 106 additions & 473 deletions

File tree

docs/INDICATOR_INVENTORY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ These are frequently used indicators with simple implementations:
267267
5. ~~**Average True Range (ATR)**~~ - **IMPLEMENTED**
268268
6. ~~**Exponential Moving Average (EMA)**~~ - **IMPLEMENTED**
269269
7. ~~**Accumulation/Distribution**~~ - **IMPLEMENTED**
270-
8. **CCI (Commodity Channel Index)** - Mean reversion
270+
8. ~~**CCI (Commodity Channel Index)**~~ - **IMPLEMENTED**
271271

272272
### Tier 2: Medium Complexity, High Utility
273273

packages/indicators/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
"version": "0.1.0",
44
"type": "module",
55
"description": "Optimized technical indicators built with OakScriptJS",
6-
"main": "dist/index.js",
6+
"main": "dist/index.cjs",
77
"module": "dist/index.mjs",
88
"types": "dist/index.d.ts",
99
"exports": {
1010
".": {
1111
"types": "./dist/index.d.ts",
1212
"import": "./dist/index.mjs",
13-
"require": "./dist/index.js"
13+
"require": "./dist/index.cjs"
1414
}
1515
},
1616
"files": [
@@ -22,7 +22,7 @@
2222
"prepublishOnly": "npm run build",
2323
"build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:types",
2424
"build:esm": "esbuild src/index.ts --bundle --format=esm --outfile=dist/index.mjs --platform=neutral --external:oakscriptjs --external:@types/*",
25-
"build:cjs": "esbuild src/index.ts --bundle --format=cjs --outfile=dist/index.js --platform=neutral --external:oakscriptjs --external:@types/*",
25+
"build:cjs": "esbuild src/index.ts --bundle --format=cjs --outfile=dist/index.cjs --platform=neutral --external:oakscriptjs --external:@types/*",
2626
"build:types": "tsc --emitDeclarationOnly --outDir dist",
2727
"clean": "rimraf dist",
2828
"dev": "tsc --watch",

packages/indicators/src/cci.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Commodity Channel Index (CCI) Indicator
3+
*
4+
* Hand-optimized implementation using oakscriptjs.
5+
* Measures the variation of a security's price from its statistical mean.
6+
* High values show the price is unusually high compared to average, low values show it's unusually low.
7+
*/
8+
9+
import { Series, ta, type IndicatorResult, type InputConfig, type PlotConfig, type Bar } from 'oakscriptjs';
10+
11+
export interface CCIInputs {
12+
length: number;
13+
src: 'open' | 'high' | 'low' | 'close' | 'hl2' | 'hlc3' | 'ohlc4' | 'hlcc4';
14+
}
15+
16+
export const defaultInputs: CCIInputs = {
17+
length: 20,
18+
src: 'hlc3',
19+
};
20+
21+
export const inputConfig: InputConfig[] = [
22+
{ id: 'length', type: 'int', title: 'Length', defval: 20, min: 1 },
23+
{ id: 'src', type: 'source', title: 'Source', defval: 'hlc3' },
24+
];
25+
26+
export const plotConfig: PlotConfig[] = [
27+
{ id: 'plot0', title: 'CCI', color: '#2962FF', lineWidth: 2 },
28+
];
29+
30+
export const metadata = {
31+
title: 'Commodity Channel Index',
32+
shortTitle: 'CCI',
33+
overlay: false,
34+
};
35+
36+
function getSourceSeries(bars: Bar[], src: CCIInputs['src']): Series {
37+
const open = new Series(bars, (bar) => bar.open);
38+
const high = new Series(bars, (bar) => bar.high);
39+
const low = new Series(bars, (bar) => bar.low);
40+
const close = new Series(bars, (bar) => bar.close);
41+
42+
switch (src) {
43+
case 'open': return open;
44+
case 'high': return high;
45+
case 'low': return low;
46+
case 'close': return close;
47+
case 'hl2': return high.add(low).div(2);
48+
case 'hlc3': return high.add(low).add(close).div(3);
49+
case 'ohlc4': return open.add(high).add(low).add(close).div(4);
50+
case 'hlcc4': return high.add(low).add(close).add(close).div(4);
51+
default: return close;
52+
}
53+
}
54+
55+
export function calculate(bars: Bar[], inputs: Partial<CCIInputs> = {}): IndicatorResult {
56+
const { length, src } = { ...defaultInputs, ...inputs };
57+
const source = getSourceSeries(bars, src);
58+
const cci = ta.cci(source, length);
59+
60+
const plotData = cci.toArray().map((value: number | undefined, i: number) => ({
61+
time: bars[i].time,
62+
value: value ?? NaN,
63+
}));
64+
65+
return {
66+
metadata: { title: metadata.title, shorttitle: metadata.shortTitle, overlay: metadata.overlay },
67+
plots: { 'plot0': plotData },
68+
};
69+
}
70+
71+
export const CCI = { calculate, metadata, defaultInputs, inputConfig, plotConfig };

packages/indicators/src/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ import * as bopIndicator from './bop';
3939
export { BOP, calculate as calculateBOP } from './bop';
4040
export type { BOPInputs } from './bop';
4141

42+
// CCI - Commodity Channel Index
43+
import * as cciIndicator from './cci';
44+
export { CCI, calculate as calculateCCI } from './cci';
45+
export type { CCIInputs } from './cci';
46+
4247
// DEMA - Double Exponential Moving Average
4348
import * as demaIndicator from './dema';
4449
export { DEMA, calculate as calculateDEMA } from './dema';
@@ -243,6 +248,18 @@ export const indicatorRegistry: IndicatorRegistryEntry[] = [
243248
defaultInputs: { ...bopIndicator.defaultInputs },
244249
calculate: bopIndicator.calculate,
245250
},
251+
{
252+
id: 'cci',
253+
name: 'Commodity Channel Index (CCI)',
254+
shortName: 'CCI',
255+
description: 'Measures the variation of a security\'s price from its statistical mean.',
256+
overlay: false,
257+
metadata: cciIndicator.metadata,
258+
inputConfig: cciIndicator.inputConfig as InputConfig[],
259+
plotConfig: cciIndicator.plotConfig as PlotConfig[],
260+
defaultInputs: { ...cciIndicator.defaultInputs },
261+
calculate: cciIndicator.calculate,
262+
},
246263
{
247264
id: 'dema',
248265
name: 'Double EMA (DEMA)',

packages/oakscriptjs/src/ta-series.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,19 @@ export function zigzag(
488488
];
489489
}
490490

491+
/**
492+
* Commodity Channel Index
493+
* @param source - Source series (typically hlc3)
494+
* @param length - Period length
495+
* @returns Series with CCI values
496+
*/
497+
export function cci(source: Series, length: number): Series {
498+
const bars = source.bars as Bar[];
499+
const sourceValues = source.toArray();
500+
const result = taCore.cci(sourceValues, length);
501+
return Series.fromArray(bars, result);
502+
}
503+
491504
// Export as namespace object to match PineScript ta.* syntax
492505
export const ta = {
493506
sma,
@@ -519,4 +532,5 @@ export const ta = {
519532
linreg,
520533
alma,
521534
zigzag,
535+
cci,
522536
};

0 commit comments

Comments
 (0)