Загрузка данных
import { HistogramData, LineData, SeriesDataItemTypeMap, Time } from 'lightweight-charts';
import { calculateEMASeriesData, calculatePreciseEMASeriesData, emaIndicator } from '@core/Indicators/ema';
import { ChartTypeToCandleData, IndicatorDataFormatter } from '@core/Indicators/index';
import { calculateMASeriesData, calculatePreciseMASeriesData, smaIndicator } from '@core/Indicators/sma';
import { getThemeStore } from '@src/theme';
type MACDMaType = 'ema' | 'sma';
export function macdSignal({
selfData,
candle,
indicatorReference,
settings,
}: IndicatorDataFormatter<'Line'>): SeriesDataItemTypeMap<Time>['Line'][] {
if (!indicatorReference) return [];
const macd = (indicatorReference.getSeriesMap().get('macdLine')?.data() ??
[]) as unknown as ChartTypeToCandleData['Line'][];
if (!macd.length) return [];
const signalLength = typeof settings?.signalLength === 'number' ? settings.signalLength : 9;
const signalMaType = getMACDMaType(settings?.signalMaType);
if (!candle) {
return signalMaType === 'sma'
? calculateMASeriesData(macd, signalLength)
: calculateEMASeriesData(macd, signalLength);
}
if (signalMaType === 'sma') {
return [calculatePreciseMASeriesData(macd, candle, signalLength)];
}
if (!selfData) {
return [{ time: candle.time as Time, value: 0 }];
}
return [calculatePreciseEMASeriesData(macd, selfData, candle, signalLength)];
}
export function macdHist({
selfData,
candle,
indicatorReference,
}: IndicatorDataFormatter<'Histogram'>): SeriesDataItemTypeMap<Time>['Histogram'][] {
if (!indicatorReference) return [];
const macd = (indicatorReference.getSeriesMap().get('macdLine')?.data() ??
[]) as unknown as ChartTypeToCandleData['Line'][];
const signal = (indicatorReference.getSeriesMap().get('signalLine')?.data() ??
[]) as unknown as ChartTypeToCandleData['Line'][];
if (!macd.length || !signal.length) return [];
const { colors } = getThemeStore();
if (!candle) {
const signalByTime = new Map<number, number>();
signal.forEach((point) => {
if (typeof point.value === 'number') {
signalByTime.set(Number(point.time), point.value);
}
});
const result: HistogramData<Time>[] = [];
macd.forEach((point) => {
if (typeof point.value !== 'number') {
return;
}
const signalValue = signalByTime.get(Number(point.time));
if (signalValue === undefined) {
return;
}
const value = point.value - signalValue;
const prevValue = result[result.length - 1]?.value ?? 0;
result.push({
value,
time: point.time as Time,
color:
value > 0
? value > prevValue
? colors.chartCandleUp
: colors.chartCandleWickUp
: value < prevValue
? colors.chartCandleDown
: colors.chartCandleWickDown,
});
});
return result;
}
const macdPoint = macd.find((point) => Number(point.time) === Number(candle.time) && typeof point.value === 'number');
const signalPoint = signal.find(
(point) => Number(point.time) === Number(candle.time) && typeof point.value === 'number',
);
if (!macdPoint || !signalPoint) {
console.error('[Indicators]: ошибка при расчете индикатора macd');
return [
{
value: 0,
time: candle.time as Time,
},
];
}
const value = macdPoint.value - signalPoint.value;
const prevPoint =
selfData[selfData.length - 1]?.time === candle.time ? selfData[selfData.length - 2] : selfData[selfData.length - 1];
const prevValue = prevPoint?.value ?? 0;
return [
{
value,
time: candle.time as Time,
color:
value > 0
? value > prevValue
? colors.chartCandleUp
: colors.chartCandleWickUp
: value < prevValue
? colors.chartCandleDown
: colors.chartCandleWickDown,
},
];
}
export function macdLine({ candle, indicatorReference }: IndicatorDataFormatter<'Line'>): LineData[] {
if (!indicatorReference) return [];
const slowMa = (indicatorReference.getSeriesMap().get('oscillatorSlowMa')?.data() ??
[]) as unknown as ChartTypeToCandleData['Line'][];
const fastMa = (indicatorReference.getSeriesMap().get('oscillatorFastMa')?.data() ??
[]) as unknown as ChartTypeToCandleData['Line'][];
if (!slowMa.length || !fastMa.length) return [];
if (!candle) {
const fastByTime = new Map<number, number>();
fastMa.forEach((point) => {
if (typeof point.value === 'number') {
fastByTime.set(Number(point.time), point.value);
}
});
const result: LineData[] = [];
slowMa.forEach((point) => {
if (typeof point.value !== 'number') {
return;
}
const fastValue = fastByTime.get(Number(point.time));
if (fastValue === undefined) {
return;
}
result.push({
value: fastValue - point.value,
time: point.time as Time,
});
});
return result;
}
const slowPoint = slowMa.find((point) => Number(point.time) === Number(candle.time) && typeof point.value === 'number');
const fastPoint = fastMa.find((point) => Number(point.time) === Number(candle.time) && typeof point.value === 'number');
if (!slowPoint || !fastPoint) {
console.error('[Indicators]: ошибка при расчете индикатора macd');
return [
{
value: 0,
time: candle.time as Time,
},
];
}
return [
{
value: fastPoint.value - slowPoint.value,
time: candle.time as Time,
},
];
}
export function macdOscillatorFastMa(params: IndicatorDataFormatter<'Line'>): SeriesDataItemTypeMap<Time>['Line'][] {
const source = getMACDSource(params.settings?.source);
const fastLength = typeof params.settings?.fastLength === 'number' ? params.settings.fastLength : 12;
const oscillatorMaType = getMACDMaType(params.settings?.oscillatorMaType);
const nextParams = {
...params,
settings: {
length: fastLength,
source,
offset: 0,
},
} as IndicatorDataFormatter<'Line'>;
return oscillatorMaType === 'sma' ? smaIndicator(nextParams) : emaIndicator(nextParams);
}
export function macdOscillatorSlowMa(params: IndicatorDataFormatter<'Line'>): SeriesDataItemTypeMap<Time>['Line'][] {
const source = getMACDSource(params.settings?.source);
const slowLength = typeof params.settings?.slowLength === 'number' ? params.settings.slowLength : 26;
const oscillatorMaType = getMACDMaType(params.settings?.oscillatorMaType);
const nextParams = {
...params,
settings: {
length: slowLength,
source,
offset: 0,
},
} as IndicatorDataFormatter<'Line'>;
return oscillatorMaType === 'sma' ? smaIndicator(nextParams) : emaIndicator(nextParams);
}
function getMACDSource(value: unknown): 'open' | 'high' | 'low' | 'close' {
return value === 'open' || value === 'high' || value === 'low' || value === 'close' ? value : 'close';
}
function getMACDMaType(value: unknown): MACDMaType {
return value === 'sma' ? 'sma' : 'ema';
}
[IndicatorsIds.MACD]: {
newPane: true,
series: [
{
name: 'Line', // todo: change with enum
id: 'oscillatorSlowMa',
dataFormatter: (params) => macdOscillatorSlowMa(params as IndicatorDataFormatter<'Line'>),
seriesOptions: {
priceScaleId: 'macd_oscillator_ma',
visible: false,
lastValueVisible: false,
color: getThemeStore().colors.chartPriceLineText,
},
},
{
name: 'Line', // todo: change with enum
id: 'oscillatorFastMa',
dataFormatter: (params) => macdOscillatorFastMa(params as IndicatorDataFormatter<'Line'>),
seriesOptions: {
priceScaleId: 'macd_oscillator_ma',
visible: false,
lastValueVisible: false,
color: getThemeStore().colors.chartPriceLineText,
},
},
{
name: 'Line', // todo: change with enum
id: 'macdLine',
priceScaleOptions: {
mode: PriceScaleMode.Normal,
},
dataFormatter: (params) => macdLine(params as IndicatorDataFormatter<'Line'>),
seriesOptions: {
priceScaleId: Direction.Right,
lastValueVisible: false,
color: getThemeStore().colors.indicatorLineSma,
},
},
{
name: 'Line', // todo: change with enum
id: 'signalLine',
priceScaleOptions: {
mode: PriceScaleMode.Normal,
},
dataFormatter: (params) => macdSignal(params as IndicatorDataFormatter<'Line'>),
seriesOptions: {
priceScaleId: Direction.Right,
lastValueVisible: false,
color: getThemeStore().colors.chartCandleWickUp,
},
},
{
name: 'Histogram', // todo: change with enum
id: 'histogram',
priceScaleOptions: {
autoScale: true,
},
seriesOptions: {
priceScaleId: Direction.Right,
lastValueVisible: false,
},
dataFormatter: (params) => macdHist(params as IndicatorDataFormatter<'Histogram'>),
},
],
settings: [
{
type: 'select',
key: 'source',
label: 'Данные',
defaultValue: 'close',
options: [
{ label: 'Цена открытия', value: 'open' },
{ label: 'Максимум', value: 'high' },
{ label: 'Минимум', value: 'low' },
{ label: 'Цена закрытия', value: 'close' },
],
},
{ type: 'number', key: 'fastLength', label: 'Длина Fast', defaultValue: 12, min: 1, max: 500 },
{ type: 'number', key: 'slowLength', label: 'Длина Slow', defaultValue: 26, min: 1, max: 500 },
{ type: 'number', key: 'signalLength', label: 'Signal length', defaultValue: 9, min: 1, max: 500 },
{
type: 'select',
key: 'oscillatorMaType',
label: 'Oscillator MA type',
defaultValue: 'ema',
options: [
{ label: 'EMA', value: 'ema' },
{ label: 'SMA', value: 'sma' },
],
},
{
type: 'select',
key: 'signalMaType',
label: 'Signal MA type',
defaultValue: 'ema',
options: [
{ label: 'EMA', value: 'ema' },
{ label: 'SMA', value: 'sma' },
],
},
],
},