-
Notifications
You must be signed in to change notification settings - Fork 186
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* bollinger mark & transform * strict & anchor for bollinger * fancy candlesticks 🕯️ * more documentation
- Loading branch information
Showing
8 changed files
with
309 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
<script setup> | ||
|
||
import * as Plot from "@observablehq/plot"; | ||
import * as d3 from "d3"; | ||
import {ref} from "vue"; | ||
import aapl from "../data/aapl.ts"; | ||
|
||
const n = ref(20); | ||
const k = ref(2); | ||
|
||
</script> | ||
|
||
# Bollinger mark | ||
|
||
The **bollinger mark** is a [composite mark](../features/marks.md#marks) consisting of a [line](./line.md) representing a moving average and an [area](./area.md) representing volatility as a band; the band thickness is proportional to the deviation of nearby values. The bollinger mark is often used to analyze the price of financial instruments such as stocks. | ||
|
||
For example, the chart below shows the price of Apple stock from 2013 to 2018, with a window size *n* of {{n}} days and radius *k* of {{k}} standard deviations. | ||
|
||
<p> | ||
<label class="label-input"> | ||
<span>Window size (n):</span> | ||
<input type="range" v-model.number="n" min="1" max="100" step="1" /> | ||
<span style="font-variant-numeric: tabular-nums;">{{n.toLocaleString("en-US")}}</span> | ||
</label> | ||
<label class="label-input"> | ||
<span>Radius (k):</span> | ||
<input type="range" v-model.number="k" min="0" max="10" step="0.1" /> | ||
<span style="font-variant-numeric: tabular-nums;">{{k.toLocaleString("en-US")}}</span> | ||
</label> | ||
</p> | ||
|
||
:::plot hidden | ||
```js | ||
Plot.bollingerY(aapl, {x: "Date", y: "Close", n, k}).plot() | ||
``` | ||
::: | ||
|
||
```js-vue | ||
Plot.bollingerY(aapl, {x: "Date", y: "Close", n: {{n}}, k: {{k}}}).plot() | ||
``` | ||
|
||
For more control, you can also use the [bollinger map method](#bollinger) directly with the [map transform](../transforms/map.md). | ||
|
||
:::plot | ||
```js | ||
Plot.plot({ | ||
marks: [ | ||
Plot.lineY(aapl, Plot.mapY(Plot.bollinger({n: 20, k: -2}), {x: "Date", y: "Close", stroke: "red"})), | ||
Plot.lineY(aapl, Plot.mapY(Plot.bollinger({n: 20, k: 2}), {x: "Date", y: "Close", stroke: "green"})), | ||
Plot.lineY(aapl, Plot.mapY(Plot.bollinger({n: 20}), {x: "Date", y: "Close"})) | ||
] | ||
}) | ||
``` | ||
::: | ||
|
||
Below a candlestick chart is constructed from two [rule marks](./rule.md), with a bollinger mark underneath to emphasize the days when the stock was more volatile. | ||
|
||
:::plot | ||
```js | ||
Plot.plot({ | ||
x: {domain: [new Date("2014-01-01"), new Date("2014-06-01")]}, | ||
y: {domain: [68, 92], grid: true}, | ||
color: {domain: [-1, 0, 1], range: ["red", "black", "green"]}, | ||
marks: [ | ||
Plot.bollingerY(aapl, {x: "Date", y: "Close", stroke: "none", clip: true}), | ||
Plot.ruleX(aapl, {x: "Date", y1: "Low", y2: "High", strokeWidth: 1, clip: true}), | ||
Plot.ruleX(aapl, {x: "Date", y1: "Open", y2: "Close", strokeWidth: 3, stroke: (d) => Math.sign(d.Close - d.Open), clip: true}) | ||
] | ||
}) | ||
``` | ||
::: | ||
|
||
The bollinger mark has two constructors: the common [bollingerY](#bollingerY) for when time goes right→ (or ←left); and the rare [bollingerX](#bollingerX) for when time goes up↑ (or down↓). | ||
|
||
:::plot | ||
```js | ||
Plot.bollingerX(aapl, {y: "Date", x: "Close"}).plot() | ||
``` | ||
::: | ||
|
||
As [shorthand](../features/shorthand.md), you can pass an array of numbers as data. Below, the *x* axis represents the zero-based index into the data (*i.e.*, trading days since May 13, 2013). | ||
|
||
:::plot | ||
```js | ||
Plot.bollingerY(aapl.map((d) => d.Close)).plot() | ||
``` | ||
::: | ||
|
||
## Bollinger options | ||
|
||
The bollinger mark is a [composite mark](../features/marks.md#marks) consisting of two marks: | ||
|
||
* an [area](../marks/area.md) representing volatility as a band, and | ||
* a [line](../marks/line.md) representing a moving average | ||
|
||
The bollinger mark supports the following special options: | ||
|
||
* **n** - the window size (the window transform’s **k** option), an integer; defaults to 20 | ||
* **k** - the band radius, a number representing a multiple of standard deviations; defaults to 2 | ||
* **color** - the fill color of the area, and the stroke color of the line; defaults to *currentColor* | ||
* **opacity** - the fill opacity of the area; defaults to 0.2 | ||
* **fill** - the fill color of the area; defaults to **color** | ||
* **fillOpacity** - the fill opacity of the area; defaults to **opacity** | ||
* **stroke** - the stroke color of the line; defaults to **color** | ||
* **strokeOpacity** - the stroke opacity of the line; defaults to 1 | ||
* **strokeWidth** - the stroke width of the line in pixels; defaults to 1.5 | ||
|
||
Any additional options are passed through to the underlying [line mark](./line.md), [area mark](./area.md), and [window transform](../transforms/window.md). Unlike the window transform, the **strict** option defaults to true, and the **anchor** option defaults to *end* (which assumes that the data is in chronological order). | ||
|
||
## bollingerX(*data*, *options*) {#bollingerX} | ||
|
||
```js | ||
Plot.bollingerX(aapl, {y: "Date", x: "Close"}) | ||
``` | ||
|
||
Returns a bollinger mark for when time goes up↑ (or down↓). If the **x** option is not specified, it defaults to the identity function, as when *data* is an array of numbers [*x₀*, *x₁*, *x₂*, …]. If the **y** option is not specified, it defaults to [0, 1, 2, …]. | ||
|
||
## bollingerY(*data*, *options*) {#bollingerY} | ||
|
||
```js | ||
Plot.bollingerY(aapl, {x: "Date", y: "Close"}) | ||
``` | ||
|
||
Returns a bollinger mark for when time goes right→ (or ←left). If the **y** option is not specified, it defaults to the identity function, as when *data* is an array of numbers [*y₀*, *y₁*, *y₂*, …]. If the **x** option is not specified, it defaults to [0, 1, 2, …]. | ||
|
||
## bollinger(*options*) {#bollinger} | ||
|
||
```js | ||
Plot.lineY(data, Plot.map({y: Plot.bollinger({n: 20})}, {x: "Date", y: "Close"})) | ||
``` | ||
|
||
Returns a bollinger map method for use with the [map transform](../transforms/map.md). The **k** option here defaults to zero instead of two. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import type {CompoundMark, Data, MarkOptions} from "../mark.js"; | ||
import type {Map} from "../transforms/map.js"; | ||
import type {WindowOptions} from "../transforms/window.js"; | ||
import type {AreaXOptions, AreaYOptions} from "./area.js"; | ||
import type {LineXOptions, LineYOptions} from "./line.js"; | ||
|
||
/** Options for the bollinger window transform. */ | ||
export interface BollingerWindowOptions { | ||
/** The number of consecutive values in the window; defaults to 20. */ | ||
n?: number; | ||
|
||
/** The number of standard deviations to offset the bands; defaults to 2. */ | ||
k?: number; | ||
|
||
/** | ||
* How to align the rolling window, placing the current value: | ||
* | ||
* - *start* - as the first element in the window | ||
* - *middle* - in the middle of the window, rounding down if **n** is even | ||
* - *end* (default) - as the last element in the window | ||
* | ||
* Note that *start* and *end* are relative to input order, not natural | ||
* ascending order by value. For example, if the data is in reverse | ||
* chronological order, then the meaning of *start* and *end* is effectively | ||
* reversed because the first data point is the most recent. | ||
*/ | ||
anchor?: WindowOptions["anchor"]; | ||
|
||
/** | ||
* If true (the default), the output start values or end values or both | ||
* (depending on the **anchor**) of each series may be undefined since there | ||
* are not enough elements to create a window of size **n**; output values may | ||
* also be undefined if some of the input values in the corresponding window | ||
* are undefined. | ||
* | ||
* If false, the window will be automatically truncated as needed, and | ||
* undefined input values are ignored. For example, if **n** is 24 and | ||
* **anchor** is *middle*, then the initial 11 values have effective window | ||
* sizes of 13, 14, 15, … 23, and likewise the last 12 values have effective | ||
* window sizes of 23, 22, 21, … 12. Values computed with a truncated window | ||
* may be noisy. | ||
*/ | ||
strict?: WindowOptions["strict"]; | ||
} | ||
|
||
/** Options for the bollinger mark. */ | ||
export interface BollingerOptions extends BollingerWindowOptions { | ||
/** | ||
* Shorthand for setting both **fill** and **stroke**; affects the stroke of | ||
* the line and the fill of the area; defaults to *currentColor*. | ||
*/ | ||
color?: MarkOptions["stroke"]; | ||
} | ||
|
||
/** Options for the bollingerX mark. */ | ||
export type BollingerXOptions = BollingerOptions & AreaXOptions & LineXOptions; | ||
|
||
/** Options for the bollingerY mark. */ | ||
export type BollingerYOptions = BollingerOptions & AreaYOptions & LineYOptions; | ||
|
||
/** | ||
* Returns a new vertically-oriented bollinger mark for the given *data* and | ||
* *options*, as in a time-series area chart where time goes up↑ (or down↓). | ||
* | ||
* If the *x* option is not specified, it defaults to the identity function, as | ||
* when data is an array of numbers [*x*₀, *x*₁, *x*₂, …]. If the *y* option is | ||
* not specified, it defaults to [0, 1, 2, …]. | ||
*/ | ||
export function bollingerX(data?: Data, options?: BollingerXOptions): CompoundMark; | ||
|
||
/** | ||
* Returns a new horizontally-oriented bollinger mark for the given *data* and | ||
* *options*, as in a time-series area chart where time goes right→ (or ←left). | ||
* | ||
* If the *y* option is not specified, it defaults to the identity function, as | ||
* when data is an array of numbers [*y*₀, *y*₁, *y*₂, …]. If the *x* option is | ||
* not specified, it defaults to [0, 1, 2, …]. | ||
*/ | ||
export function bollingerY(data?: Data, options?: BollingerYOptions): CompoundMark; | ||
|
||
/** | ||
* Given the specified bollinger *options*, returns a corresponding map | ||
* implementation for use with the map transform, allowing the bollinger | ||
* transform to be applied to arbitrary channels instead of only *x* and *y*. | ||
* For example, to compute the upper volatility band: | ||
* | ||
* ```js | ||
* Plot.map({y: Plot.bollinger({n: 20, k: 2})}, {x: "Date", y: "Close"}) | ||
* ``` | ||
* | ||
* Here the *k* option defaults to zero instead of two. | ||
*/ | ||
export function bollinger(options?: BollingerWindowOptions): Map; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import {deviation, mean} from "d3"; | ||
import {marks} from "../mark.js"; | ||
import {map} from "../transforms/map.js"; | ||
import {window} from "../transforms/window.js"; | ||
import {areaX, areaY} from "./area.js"; | ||
import {lineX, lineY} from "./line.js"; | ||
import {identity} from "../options.js"; | ||
|
||
const defaults = { | ||
n: 20, | ||
k: 2, | ||
color: "currentColor", | ||
opacity: 0.2, | ||
strict: true, | ||
anchor: "end" | ||
}; | ||
|
||
export function bollingerX( | ||
data, | ||
{ | ||
x = identity, | ||
y, | ||
k = defaults.k, | ||
color = defaults.color, | ||
opacity = defaults.opacity, | ||
fill = color, | ||
fillOpacity = opacity, | ||
stroke = color, | ||
strokeOpacity, | ||
strokeWidth, | ||
...options | ||
} = {} | ||
) { | ||
return marks( | ||
areaX( | ||
data, | ||
map( | ||
{x1: bollinger({k: -k, ...options}), x2: bollinger({k, ...options})}, | ||
{x1: x, x2: x, y, fill, fillOpacity, ...options} | ||
) | ||
), | ||
lineX(data, map({x: bollinger(options)}, {x, y, stroke, strokeOpacity, strokeWidth, ...options})) | ||
); | ||
} | ||
|
||
export function bollingerY( | ||
data, | ||
{ | ||
x, | ||
y = identity, | ||
k = defaults.k, | ||
color = defaults.color, | ||
opacity = defaults.opacity, | ||
fill = color, | ||
fillOpacity = opacity, | ||
stroke = color, | ||
strokeOpacity, | ||
strokeWidth, | ||
...options | ||
} = {} | ||
) { | ||
return marks( | ||
areaY( | ||
data, | ||
map( | ||
{y1: bollinger({k: -k, ...options}), y2: bollinger({k, ...options})}, | ||
{x, y1: y, y2: y, fill, fillOpacity, ...options} | ||
) | ||
), | ||
lineY(data, map({y: bollinger(options)}, {x, y, stroke, strokeOpacity, strokeWidth, ...options})) | ||
); | ||
} | ||
|
||
export function bollinger({n = defaults.n, k = 0, strict = defaults.strict, anchor = defaults.anchor} = {}) { | ||
return window({k: n, reduce: (Y) => mean(Y) + k * (deviation(Y) || 0), strict, anchor}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters