PointAndFigureFilter
PointAndFigureFilterš converts OHLC close values into Point & Figure marks. It outputs an XyDataSeries where X is the Point & Figure column index and Y is the mark price, and it exposes the full calculation through lastResultš.
- TS
function createLayout(divElementId) {
const root = document.getElementById(divElementId);
root.style.cssText = "width:100%;height:100%;min-height:560px;display:grid;grid-template-rows:auto minmax(0,1fr);";
root.innerHTML = `
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:8px;font-family:Arial,sans-serif;color:#888;">
<strong style="font-size:14px;">Point & Figure filter</strong>
<button id="${divElementId}-source">Source close</button>
<button id="${divElementId}-filter">Point & Figure</button>
<label style="font-size:13px;">Box <input id="${divElementId}-box" type="number" min="0.001" max="0.02" step="0.001" value="${DEFAULT_BOX_SIZE}" style="width:76px;"></label>
<label style="font-size:13px;">Reversal <input id="${divElementId}-reversal" type="number" min="1" max="5" step="1" value="${DEFAULT_REVERSAL_AMOUNT}" style="width:52px;"></label>
<span id="${divElementId}-count" style="font-size:13px;opacity:0.75;"></span>
</div>
<div id="${divElementId}-chart" style="width:100%;height:100%;min-height:0;"></div>
`;
return `${divElementId}-chart`;
}
async function drawExample(divElementId) {
const {
EllipsePointMarker,
FastLineRenderableSeries,
MouseWheelZoomModifier,
NumberRange,
NumericAxis,
OhlcDataSeries,
RolloverModifier,
SciChartSurface,
XPointMarker,
XyDataSeries,
XyScatterRenderableSeries,
ZoomExtentsModifier,
ZoomPanModifier
} = SciChart; // or import from "scichart"
const {
PointAndFigureFilter,
SciTraderLightTheme,
} = SciChartFinancialTools; // if using npm, import from "scichart-financial-tools";
const chartDivId = createLayout(divElementId);
const { sciChartSurface, wasmContext } = await SciChartSurface.create(chartDivId, {
theme: new SciTraderLightTheme()
});
const xAxis = new NumericAxis(wasmContext, {
drawMajorGridLines: false,
drawMajorBands: false,
minorsPerMajor: 2,
axisTitle: "P&F Column",
growBy: new NumberRange(0.2, 0.2)
});
const yAxis = new NumericAxis(wasmContext, {
drawMajorGridLines: false,
drawMajorBands: false,
minorsPerMajor: 2,
axisTitle: "Price",
growBy: new NumberRange(0.02, 0.02),
labelPrecision: 3
});
sciChartSurface.xAxes.add(xAxis);
sciChartSurface.yAxes.add(yAxis);
const sourceSeries = new OhlcDataSeries(wasmContext, {
xValues: sourceData.xValues,
openValues: sourceData.openValues,
highValues: sourceData.highValues,
lowValues: sourceData.lowValues,
closeValues: sourceData.closeValues,
dataSeriesName: "P&F Source OHLC"
});
const pointAndFigureFilter = new PointAndFigureFilter(sourceSeries, {
boxSize: DEFAULT_BOX_SIZE,
reversalAmount: DEFAULT_REVERSAL_AMOUNT,
dataSeriesName: "Point & Figure Marks"
});
const sourceCloseSeries = new XyDataSeries(wasmContext, {
xValues: sourceData.closeValues.map((_, index) => index),
yValues: sourceData.closeValues,
dataSeriesName: "Source Close"
});
const xMarkSeries = new XyDataSeries(wasmContext, { dataSeriesName: "Rising X" });
const oMarkSeries = new XyDataSeries(wasmContext, { dataSeriesName: "Falling O" });
const sourceLine = new FastLineRenderableSeries(wasmContext, {
dataSeries: sourceCloseSeries,
stroke: "#8EA7FF",
strokeThickness: 2
});
const xMarks = new XyScatterRenderableSeries(wasmContext, {
dataSeries: xMarkSeries,
pointMarker: new XPointMarker(wasmContext, { width: 14, height: 14, stroke: "#38D39F", strokeThickness: 2 })
});
const oMarks = new XyScatterRenderableSeries(wasmContext, {
dataSeries: oMarkSeries,
pointMarker: new EllipsePointMarker(wasmContext, {
width: 13,
height: 13,
stroke: "#FF6B6B",
fill: "transparent",
strokeThickness: 2
})
});
sciChartSurface.renderableSeries.add(sourceLine, xMarks, oMarks);
sciChartSurface.chartModifiers.add(
new ZoomPanModifier(),
new ZoomExtentsModifier(),
new MouseWheelZoomModifier(),
);
let usingPointAndFigure = true;
const countLabel = document.getElementById(`${divElementId}-count`);
const syncPointAndFigureSeries = () => {
const result = pointAndFigureFilter.lastResult;
xMarkSeries.clear();
oMarkSeries.clear();
xMarkSeries.appendRange(
result.xMarks.map(mark => mark.columnIndex),
result.xMarks.map(mark => mark.price)
);
oMarkSeries.appendRange(
result.oMarks.map(mark => mark.columnIndex),
result.oMarks.map(mark => mark.price)
);
countLabel.textContent = `${result.markCount} marks in ${result.columnCount} columns`;
};
const applyMode = () => {
syncPointAndFigureSeries();
sourceLine.isVisible = !usingPointAndFigure;
xMarks.isVisible = usingPointAndFigure;
oMarks.isVisible = usingPointAndFigure;
xAxis.axisTitle = usingPointAndFigure ? "P&F Column" : "Source Index";
sciChartSurface.zoomExtents(300);
};
document.getElementById(`${divElementId}-source`).onclick = () => {
usingPointAndFigure = false;
applyMode();
};
document.getElementById(`${divElementId}-filter`).onclick = () => {
usingPointAndFigure = true;
applyMode();
};
document.getElementById(`${divElementId}-box`).oninput = event => {
if (event.target instanceof HTMLInputElement) {
pointAndFigureFilter.boxSize = Number(event.target.value);
applyMode();
}
};
document.getElementById(`${divElementId}-reversal`).oninput = event => {
if (event.target instanceof HTMLInputElement) {
pointAndFigureFilter.reversalAmount = Number(event.target.value);
applyMode();
}
};
applyMode();
}
Use boxSizeš to control the price distance represented by one box and reversalAmountš to control how many boxes are required before a new opposite-direction column begins.