mirror of
https://git.hmsn.ink/coin/chart.git
synced 2026-03-20 00:02:17 +09:00
rsi 추가
This commit is contained in:
57
src/App.vue
57
src/App.vue
@@ -10,7 +10,9 @@ import { ref } from 'vue';
|
|||||||
*/
|
*/
|
||||||
import LWChart from './components/LWChart.vue';
|
import LWChart from './components/LWChart.vue';
|
||||||
import {getCandleList} from '/@src/utils/api'
|
import {getCandleList} from '/@src/utils/api'
|
||||||
import {calculateMACD, calculateEMA, calculateBollingerBands} from "./utils/meter.js";
|
import {calculateMACD, calculateEMA, calculateBollingerBands, calculateRSI} from "./utils/meter.js";
|
||||||
|
import {entrySignals} from "./utils/cro.js";
|
||||||
|
import {swingSignal} from "./utils/sig.js";
|
||||||
/**
|
/**
|
||||||
* Generates sample data for the lightweight chart
|
* Generates sample data for the lightweight chart
|
||||||
* @param {Boolean} ohlc Whether generated dat should include open, high, low, and close values
|
* @param {Boolean} ohlc Whether generated dat should include open, high, low, and close values
|
||||||
@@ -21,7 +23,8 @@ const lazyLock = ref(false)
|
|||||||
const positionData = ref('')
|
const positionData = ref('')
|
||||||
|
|
||||||
const contract = ref('BTC_USDT')
|
const contract = ref('BTC_USDT')
|
||||||
const time = ref('15m')
|
/* Interval : "10s", "1m", "5m", "15m", "30m", "1h", "4h", "8h", "1d", "7d"*/
|
||||||
|
const time = ref('1h')
|
||||||
const chartOptions = ref({
|
const chartOptions = ref({
|
||||||
layout: {
|
layout: {
|
||||||
textColor: 'black',
|
textColor: 'black',
|
||||||
@@ -42,6 +45,7 @@ const candleTick = ref([])
|
|||||||
const ema = ref([])
|
const ema = ref([])
|
||||||
const macd = ref({})
|
const macd = ref({})
|
||||||
const bb = ref({})
|
const bb = ref({})
|
||||||
|
const rsi = ref({})
|
||||||
|
|
||||||
const selectCoins = [
|
const selectCoins = [
|
||||||
{ title: 'BTC_USDT'},
|
{ title: 'BTC_USDT'},
|
||||||
@@ -87,17 +91,20 @@ const candleOptions = ref({
|
|||||||
|
|
||||||
const macdLineOptions = ref({
|
const macdLineOptions = ref({
|
||||||
lineWidth: 2,
|
lineWidth: 2,
|
||||||
color: '#FFBAB5'
|
color: '#FFBAB5',
|
||||||
|
title: 'MACD',
|
||||||
});
|
});
|
||||||
|
|
||||||
const macdSignalOptions = ref({
|
const macdSignalOptions = ref({
|
||||||
lineWidth: 2,
|
lineWidth: 2,
|
||||||
color: '#AD81FF'
|
color: '#AD81FF',
|
||||||
|
title: 'SIGNAL',
|
||||||
});
|
});
|
||||||
|
|
||||||
const macdHistogramOptions = ref({
|
const macdHistogramOptions = ref({
|
||||||
lineWidth: 2,
|
lineWidth: 2,
|
||||||
color: '#27FFE6'
|
color: '#27FFE6',
|
||||||
|
title: 'HISTOGRAM',
|
||||||
});
|
});
|
||||||
|
|
||||||
const upperBbOptions = ref({
|
const upperBbOptions = ref({
|
||||||
@@ -121,6 +128,12 @@ const lowerBbOptions = ref({
|
|||||||
title: 'LowerBB',
|
title: 'LowerBB',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const rsiLineOptions = ref({
|
||||||
|
lineWidth: 2,
|
||||||
|
color: '#AD81FF',
|
||||||
|
title: 'RSI',
|
||||||
|
});
|
||||||
|
|
||||||
const chartType = ref('candlestick');
|
const chartType = ref('candlestick');
|
||||||
const lwChart = ref();
|
const lwChart = ref();
|
||||||
|
|
||||||
@@ -130,14 +143,16 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const init = (cont, ti) => {
|
const init = (cont, ti) => {
|
||||||
socket = new WebSocket("ws://127.0.0.1:8765");
|
socket = new WebSocket("wss://fx-ws.gateio.ws/v4/ws/usdt");
|
||||||
const dt = new Date()
|
const dt = new Date()
|
||||||
getCandleList(cont, ti, Math.round(dt.getTime()/1000)).then(data => {
|
getCandleList(cont, ti, Math.round(dt.getTime()/1000)).then(data => {
|
||||||
ema.value = calculateEMA(data, 20)
|
ema.value = calculateEMA(data, 20)
|
||||||
macd.value = calculateMACD(data)
|
macd.value = calculateMACD(data)
|
||||||
bb.value = calculateBollingerBands(data)
|
bb.value = calculateBollingerBands(data)
|
||||||
|
rsi.value = calculateRSI(data)
|
||||||
|
// console.log(swingSignal(data))
|
||||||
|
// console.table(entrySignals(data))
|
||||||
candleTick.value = data
|
candleTick.value = data
|
||||||
console.log(bb.value)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
candleSocket(cont, ti)
|
candleSocket(cont, ti)
|
||||||
@@ -145,6 +160,7 @@ const init = (cont, ti) => {
|
|||||||
|
|
||||||
const distroy = (cont) => {
|
const distroy = (cont) => {
|
||||||
candleTick.value = []
|
candleTick.value = []
|
||||||
|
socket.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
const lazyLoad = async (ti) => {
|
const lazyLoad = async (ti) => {
|
||||||
@@ -154,6 +170,7 @@ const lazyLoad = async (ti) => {
|
|||||||
ema.value = calculateEMA(data, 20)
|
ema.value = calculateEMA(data, 20)
|
||||||
macd.value = calculateMACD(data)
|
macd.value = calculateMACD(data)
|
||||||
bb.value = calculateBollingerBands(data)
|
bb.value = calculateBollingerBands(data)
|
||||||
|
rsi.value = calculateRSI(data)
|
||||||
setTimeout(() => {lazyLock.value = false}, 3000)
|
setTimeout(() => {lazyLock.value = false}, 3000)
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -161,12 +178,15 @@ const lazyLoad = async (ti) => {
|
|||||||
|
|
||||||
const candleSocket = (cont, ti) => {
|
const candleSocket = (cont, ti) => {
|
||||||
socket.addEventListener('open', (event) => {
|
socket.addEventListener('open', (event) => {
|
||||||
socket.send(JSON.stringify({"type":"subscribe", "channel": cont, "time": ti}))
|
// socket.send(JSON.stringify({"type":"subscribe", "channel": cont, "time": ti}))
|
||||||
|
console.log(cont, ti)
|
||||||
|
socket.send(JSON.stringify({"time" : curToUnix(), "channel" : "futures.candlesticks","event": "subscribe", "payload" : [ti, cont]}))
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.addEventListener('message', (message) => {
|
socket.addEventListener('message', (message) => {
|
||||||
const msg = JSON.parse(message.data)
|
const msg = JSON.parse(message.data)
|
||||||
const item = JSON.parse(msg.msg)
|
if(msg.event === 'update') {
|
||||||
|
const item = msg.result[0]
|
||||||
|
|
||||||
const ticks = candleTick.value
|
const ticks = candleTick.value
|
||||||
if(item.t === ticks[ticks.length - 1].t) {
|
if(item.t === ticks[ticks.length - 1].t) {
|
||||||
@@ -180,7 +200,12 @@ const candleSocket = (cont, ti) => {
|
|||||||
ema.value = calculateEMA(ticks, 20)
|
ema.value = calculateEMA(ticks, 20)
|
||||||
macd.value = calculateMACD(ticks)
|
macd.value = calculateMACD(ticks)
|
||||||
bb.value = calculateBollingerBands(ticks)
|
bb.value = calculateBollingerBands(ticks)
|
||||||
|
rsi.value = calculateRSI(ticks)
|
||||||
|
// console.table(entrySignals(ticks))
|
||||||
|
console.log(swingSignal(ticks))
|
||||||
lazyLock.value = false
|
lazyLock.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -233,23 +258,21 @@ const colorsTypeMap = {
|
|||||||
|
|
||||||
const mouseMove = (data) => {
|
const mouseMove = (data) => {
|
||||||
positionData.value = `
|
positionData.value = `
|
||||||
<span style="color:red; padding-right:3px;">O</span>${data.open}
|
<span style="color:red; padding-right:3px;">O</span>${data == null ? 0 : data.open}
|
||||||
<span style="color:red; padding-left:5px; padding-right:3px;">C</span>${data.close}
|
<span style="color:red; padding-left:5px; padding-right:3px;">C</span>${data == null ? 0 : data.close}
|
||||||
<span style="color:red; padding-left:5px; padding-right:3px;">L</span>${data.low}
|
<span style="color:red; padding-left:5px; padding-right:3px;">L</span>${data == null ? 0 : data.low}
|
||||||
<span style="color:red; padding-left:5px; padding-right:3px;">H</span>${data.high}
|
<span style="color:red; padding-left:5px; padding-right:3px;">H</span>${data == null ? 0 : data.high}
|
||||||
<span style="color:red; padding-left:5px; padding-right:3px;">T</span>${epochToString(data.time*1000)}
|
<span style="color:red; padding-left:5px; padding-right:3px;">T</span>${data == null ? 0 : epochToString(data.time*1000)}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(contract, newVal => {
|
watch(contract, newVal => {
|
||||||
distroy()
|
distroy()
|
||||||
console.log(newVal);
|
|
||||||
init(newVal, time.value)
|
init(newVal, time.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(time, newVal => {
|
watch(time, newVal => {
|
||||||
distroy()
|
distroy()
|
||||||
console.log(newVal);
|
|
||||||
init(contract.value, newVal)
|
init(contract.value, newVal)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -303,6 +326,7 @@ watch(time, newVal => {
|
|||||||
:ema="ema"
|
:ema="ema"
|
||||||
:macd="macd"
|
:macd="macd"
|
||||||
:bb="bb"
|
:bb="bb"
|
||||||
|
:rsi="rsi"
|
||||||
:autosize="true"
|
:autosize="true"
|
||||||
:chart-options="chartOptions"
|
:chart-options="chartOptions"
|
||||||
:ema-options="emaLineOptions"
|
:ema-options="emaLineOptions"
|
||||||
@@ -313,6 +337,7 @@ watch(time, newVal => {
|
|||||||
:lower-bb-options="lowerBbOptions"
|
:lower-bb-options="lowerBbOptions"
|
||||||
:middle-bb-options="middleBbOptions"
|
:middle-bb-options="middleBbOptions"
|
||||||
:upper-bb-options="upperBbOptions"
|
:upper-bb-options="upperBbOptions"
|
||||||
|
:rsi-line-options="rsiLineOptions"
|
||||||
:lazyLock="lazyLock"
|
:lazyLock="lazyLock"
|
||||||
@lazyLoad="lazyLoad"
|
@lazyLoad="lazyLoad"
|
||||||
@mouseMove="mouseMove"
|
@mouseMove="mouseMove"
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ const props = defineProps({
|
|||||||
bb: {
|
bb: {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
|
rsi: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
autosize: {
|
autosize: {
|
||||||
default: true,
|
default: true,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -58,6 +61,9 @@ const props = defineProps({
|
|||||||
macdHistogramOptions: {
|
macdHistogramOptions: {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
|
rsiLineOptions: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
upperBbOptions: {
|
upperBbOptions: {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
@@ -108,13 +114,15 @@ let chart;
|
|||||||
let macdLine;
|
let macdLine;
|
||||||
let macdSignalLine;
|
let macdSignalLine;
|
||||||
let macdHistogram;
|
let macdHistogram;
|
||||||
let mainPane;
|
|
||||||
let macdPane;
|
let macdPane;
|
||||||
|
let rsiPane;
|
||||||
|
|
||||||
let upperBb;
|
let upperBb;
|
||||||
let middleBb;
|
let middleBb;
|
||||||
let lowerBb;
|
let lowerBb;
|
||||||
|
|
||||||
|
let rsiLine;
|
||||||
|
|
||||||
const chartContainer = ref();
|
const chartContainer = ref();
|
||||||
|
|
||||||
const fitContent = () => {
|
const fitContent = () => {
|
||||||
@@ -160,7 +168,6 @@ const addSeriesAndData = props => {
|
|||||||
emaLine = chart.addSeries(seriesDefinition, props.emaOptions)
|
emaLine = chart.addSeries(seriesDefinition, props.emaOptions)
|
||||||
emaLine.setData(props.ema)
|
emaLine.setData(props.ema)
|
||||||
|
|
||||||
console.log(Object.keys(props.macd).length)
|
|
||||||
macdLine = macdPane.addSeries(seriesDefinition, props.macdLineOptions)
|
macdLine = macdPane.addSeries(seriesDefinition, props.macdLineOptions)
|
||||||
macdSignalLine = macdPane.addSeries(seriesDefinition, props.macdSignalOptions)
|
macdSignalLine = macdPane.addSeries(seriesDefinition, props.macdSignalOptions)
|
||||||
macdHistogram = macdPane.addSeries(getChartSeriesDefinition('histogram'), props.macdHistogramOptions)
|
macdHistogram = macdPane.addSeries(getChartSeriesDefinition('histogram'), props.macdHistogramOptions)
|
||||||
@@ -178,6 +185,11 @@ const addSeriesAndData = props => {
|
|||||||
middleBb.setData(props.bb.m);
|
middleBb.setData(props.bb.m);
|
||||||
lowerBb.setData(props.bb.l);
|
lowerBb.setData(props.bb.l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rsiLine = rsiPane.addSeries(seriesDefinition, props.rsiLineOptions)
|
||||||
|
if(Object.keys(props.macd).length) {
|
||||||
|
rsiLine.setData(props.rsi)
|
||||||
|
}
|
||||||
// console.log(3)
|
// console.log(3)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -186,6 +198,8 @@ onMounted(() => {
|
|||||||
chart = createChart(chartContainer.value, props.chartOptions);
|
chart = createChart(chartContainer.value, props.chartOptions);
|
||||||
macdPane = chart.addPane();
|
macdPane = chart.addPane();
|
||||||
macdPane.setStretchFactor(0.5)
|
macdPane.setStretchFactor(0.5)
|
||||||
|
rsiPane = chart.addPane();
|
||||||
|
rsiPane.setStretchFactor(0.4)
|
||||||
|
|
||||||
addSeriesAndData(props);
|
addSeriesAndData(props);
|
||||||
|
|
||||||
@@ -325,6 +339,15 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.rsi,
|
||||||
|
newData => {
|
||||||
|
if (!rsiLine) return;
|
||||||
|
rsiLine.setData(props.rsi);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.chartOptions,
|
() => props.chartOptions,
|
||||||
newOptions => {
|
newOptions => {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {useAxios} from "@vueuse/integrations/useAxios";
|
import {useAxios} from "@vueuse/integrations/useAxios";
|
||||||
|
|
||||||
export const getCandleList = (contact:string, time: string, timestamp: number) => {
|
export const getCandleList = (contract:string, time: string, timestamp: number) => {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
useAxios(`/api/candle/${contact}/${time}/${timestamp}`).then((res) => {
|
useAxios(`/api/v4/futures/usdt/candlesticks?contract=${contract}&interval=${time}&to=${timestamp}&limit=1500`).then((res) => {
|
||||||
resolve(res.data.value)
|
resolve(res.data.value)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
145
src/utils/cro.ts
Normal file
145
src/utils/cro.ts
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
|
||||||
|
function mean(arr: any) {
|
||||||
|
return arr.reduce((a: number, b: any) => a + b, 0) / arr.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sum(arr: any) {
|
||||||
|
return arr.reduce((a: number, b: any) => a + b, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function atr(data:any, period: number) {
|
||||||
|
const trs = [];
|
||||||
|
for (let i = 1; i < data.length; i++) {
|
||||||
|
const prev = data[i - 1];
|
||||||
|
const cur = data[i];
|
||||||
|
const tr = Math.max(
|
||||||
|
cur.h - cur.l,
|
||||||
|
Math.abs(cur.h - prev.c),
|
||||||
|
Math.abs(cur.l - prev.c)
|
||||||
|
);
|
||||||
|
trs.push(tr);
|
||||||
|
}
|
||||||
|
const atrArr = [];
|
||||||
|
for (let i = period - 1; i < trs.length; i++) {
|
||||||
|
const slice = trs.slice(i - period + 1, i + 1);
|
||||||
|
atrArr.push(mean(slice));
|
||||||
|
}
|
||||||
|
return atrArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function vwap(data:any, period: number) {
|
||||||
|
// 일반 VWAP = ∑(typical * volume) / ∑volume
|
||||||
|
const vwaps = [];
|
||||||
|
for (let i = period - 1; i < data.length; i++) {
|
||||||
|
const slice = data.slice(i - period + 1, i + 1);
|
||||||
|
let num = 0,
|
||||||
|
den = 0;
|
||||||
|
slice.forEach((c: any) => {
|
||||||
|
const typical = (Number(c.h) + Number(c.l) + Number(c.c)) / 3;
|
||||||
|
num += typical * Number(c.v);
|
||||||
|
den += Number(c.v);
|
||||||
|
});
|
||||||
|
vwaps.push(num / den);
|
||||||
|
}
|
||||||
|
return vwaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function rsi(data: any, period: number) {
|
||||||
|
const gains = [],
|
||||||
|
losses = [];
|
||||||
|
for (let i = 1; i < data.length; i++) {
|
||||||
|
const diff = Number(data[i].c) - Number(data[i - 1].c);
|
||||||
|
gains.push(diff > 0 ? diff : 0);
|
||||||
|
losses.push(diff < 0 ? -diff : 0);
|
||||||
|
}
|
||||||
|
const rsiArr = [];
|
||||||
|
for (let i = period; i < gains.length; i++) {
|
||||||
|
const avgGain = mean(gains.slice(i - period, i));
|
||||||
|
const avgLoss = mean(losses.slice(i - period, i));
|
||||||
|
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
||||||
|
rsiArr.push(100 - 100 / (1 + rs));
|
||||||
|
}
|
||||||
|
return rsiArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bb(data: any, period: number, mult = 2) {
|
||||||
|
const up = [],
|
||||||
|
mid = [],
|
||||||
|
low = [];
|
||||||
|
for (let i = period - 1; i < data.length; i++) {
|
||||||
|
const slice = data.slice(i - period + 1, i + 1).map((x: any) => Number(x.c));
|
||||||
|
const m = mean(slice);
|
||||||
|
const stdev =
|
||||||
|
Math.sqrt(sum(slice.map((p: any) => Math.pow(p - m, 2))) / period) || 0;
|
||||||
|
mid.push(m);
|
||||||
|
up.push(m + mult * stdev);
|
||||||
|
low.push(m - mult * stdev);
|
||||||
|
}
|
||||||
|
return { upper: up, middle: mid, lower: low };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function calculateCRO(data: any) {
|
||||||
|
const n = data.length;
|
||||||
|
const vw = vwap(data, 20);
|
||||||
|
const mom = [];
|
||||||
|
for (let i = 20; i < n; i++) {
|
||||||
|
mom.push(data[i].c / data[i - 20].c - 1);
|
||||||
|
}
|
||||||
|
const divergences = [];
|
||||||
|
for (let i = 0; i < mom.length; i++) {
|
||||||
|
const idx20 = 19 + i;
|
||||||
|
const v = Math.abs(mom[i]) - Math.abs((vw[i] - data[idx20].c) / data[idx20].c);
|
||||||
|
divergences.push(v);
|
||||||
|
}
|
||||||
|
const atr14 = atr(data, 14);
|
||||||
|
const atr63 = atr(data, 63);
|
||||||
|
const vsr = [];
|
||||||
|
for (let i = 0; i < atr14.length; i++) {
|
||||||
|
const a63 = atr63[i] || 1;
|
||||||
|
vsr.push(atr14[i] / a63);
|
||||||
|
}
|
||||||
|
// offset 맞추기
|
||||||
|
const offset = 63 - 1;
|
||||||
|
const cro = [];
|
||||||
|
for (let i = offset; i < n; i++) {
|
||||||
|
const di = i - offset;
|
||||||
|
const d = divergences[di] || 0;
|
||||||
|
const v = vsr[di - (63 - 14)] || 1;
|
||||||
|
let val = 0;
|
||||||
|
if (d >= 0.015 && v >= 1.2) val = 1;
|
||||||
|
if (d <= -0.015 && v >= 1.2) val = -1;
|
||||||
|
cro.push({ t: data[i].t, cro: val, divergence: d, vsr: v });
|
||||||
|
}
|
||||||
|
return cro;
|
||||||
|
}
|
||||||
|
|
||||||
|
function entrySignals(data: any) {
|
||||||
|
const cro = calculateCRO(data);
|
||||||
|
const rsi14 = rsi(data, 14);
|
||||||
|
const bbands = bb(data, 20, 2);
|
||||||
|
|
||||||
|
const signals = [];
|
||||||
|
const croOffset = data.length - cro.length;
|
||||||
|
const rsiOffset = data.length - rsi14.length;
|
||||||
|
const bbOffset = data.length - bbands.lower.length;
|
||||||
|
for (let i = 0; i < cro.length; i++) {
|
||||||
|
const idx = croOffset + i;
|
||||||
|
const cur = data[idx];
|
||||||
|
const r = rsi14[i - (croOffset - rsiOffset)];
|
||||||
|
const bbl = bbands.lower[i - (croOffset - bbOffset)];
|
||||||
|
const bbu = bbands.upper[i - (croOffset - bbOffset)];
|
||||||
|
|
||||||
|
if (cro[i].cro === 1 && r <= 35 && cur.c <= bbl) {
|
||||||
|
signals.push({ t: cro[i].t, side: 'LONG', price: cur.c });
|
||||||
|
}
|
||||||
|
if (cro[i].cro === -1 && r >= 65 && cur.c >= bbu) {
|
||||||
|
signals.push({ t: cro[i].t, side: 'SHORT', price: cur.c });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return signals;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {entrySignals}
|
||||||
@@ -24,6 +24,33 @@ function calculateBollingerBands(data: any, period = 20, stdDevMultiplier = 2) {
|
|||||||
return { u, m, l };
|
return { u, m, l };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function calculateRSI(data: any, period = 14) {
|
||||||
|
function mean(arr: any) {
|
||||||
|
return arr.reduce((a: number, b: any) => a + b, 0) / arr.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gains = [],
|
||||||
|
losses = [];
|
||||||
|
for (let i = 1; i < data.length; i++) {
|
||||||
|
const diff = Number(data[i].c) - Number(data[i - 1].c);
|
||||||
|
gains.push(diff > 0 ? diff : 0);
|
||||||
|
losses.push(diff < 0 ? -diff : 0);
|
||||||
|
}
|
||||||
|
const rsiArr = [];
|
||||||
|
for (let i = 0; i < gains.length; i++) {
|
||||||
|
const avgGain = mean(gains.slice(i - period, i));
|
||||||
|
const avgLoss = mean(losses.slice(i - period, i));
|
||||||
|
const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
|
||||||
|
if(isNaN(rs)) {
|
||||||
|
rsiArr.push({time: data[i].t ?? data[i].time, value: 0});
|
||||||
|
} else {
|
||||||
|
rsiArr.push({time: data[i].t ?? data[i].time, value: 100 - 100 / (1 + rs)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rsiArr;
|
||||||
|
}
|
||||||
|
|
||||||
function calculateEMA(data:any, period:any) {
|
function calculateEMA(data:any, period:any) {
|
||||||
const ema = [];
|
const ema = [];
|
||||||
const k = 2 / (period + 1);
|
const k = 2 / (period + 1);
|
||||||
@@ -73,4 +100,4 @@ function calculateMACD(data: any, fastPeriod = 12, slowPeriod = 26, signalPeriod
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export { calculateBollingerBands, calculateEMA, calculateMACD };
|
export { calculateBollingerBands, calculateEMA, calculateMACD, calculateRSI };
|
||||||
146
src/utils/sig.ts
Normal file
146
src/utils/sig.ts
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
|
||||||
|
// ---------- 헬퍼 ----------
|
||||||
|
const mean = (arr: any) => arr.reduce((a:number, b:number) => a + b, 0) / arr.length;
|
||||||
|
const sum = (arr: any) => arr.reduce((a:number, b:number) => a + b, 0);
|
||||||
|
|
||||||
|
// ---------- 기술지표 ----------
|
||||||
|
const atr = (data: any, period: number) => {
|
||||||
|
const trs = [];
|
||||||
|
for (let i = 1; i < data.length; i++) {
|
||||||
|
const prev = data[i-1], cur = data[i];
|
||||||
|
const tr = Math.max(Number(cur.h)-Number(cur.l), Math.abs(Number(cur.h)-Number(prev.c)), Math.abs(Number(cur.l)-Number(prev.c)));
|
||||||
|
trs.push(tr);
|
||||||
|
}
|
||||||
|
const res = [];
|
||||||
|
for (let i=period-1;i<trs.length;i++){
|
||||||
|
res.push(mean(trs.slice(i-period+1,i+1)));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rsi = (data: any, period: number) => {
|
||||||
|
const gains=[], losses=[];
|
||||||
|
for(let i=1;i<data.length;i++){
|
||||||
|
const d = Number(data[i].c) - Number(data[i-1].c);
|
||||||
|
gains.push(d>0?d:0); losses.push(d<0?-d:0);
|
||||||
|
}
|
||||||
|
const res=[];
|
||||||
|
for(let i=period;i<gains.length;i++){
|
||||||
|
const ag=mean(gains.slice(i-period,i));
|
||||||
|
const al=mean(losses.slice(i-period,i));
|
||||||
|
const rs = al===0?100:ag/al;
|
||||||
|
res.push(100-100/(1+rs));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
const bb = (data: any, period: number, mult=2) => {
|
||||||
|
const up=[], mid=[], low=[];
|
||||||
|
for(let i=period-1;i<data.length;i++){
|
||||||
|
const slice=data.slice(i-period+1,i+1).map((x: any)=>Number(x.c));
|
||||||
|
const m=mean(slice);
|
||||||
|
const stdev=Math.sqrt(sum(slice.map((p: number)=>(p-m)**2))/period);
|
||||||
|
mid.push(m); up.push(m+mult*stdev); low.push(m-mult*stdev);
|
||||||
|
}
|
||||||
|
return {upper:up, middle:mid, lower:low};
|
||||||
|
};
|
||||||
|
|
||||||
|
const macd = (data: any) => {
|
||||||
|
const fast = 12, slow = 26, sig = 9;
|
||||||
|
|
||||||
|
// 1) EMA 헬퍼
|
||||||
|
const ema = (arr: any, len: number) => {
|
||||||
|
const k = 2 / (len + 1);
|
||||||
|
const out = [arr[0]];
|
||||||
|
for (let i = 1; i < arr.length; i++) {
|
||||||
|
out.push(arr[i] * k + out[i - 1] * (1 - k));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2) MACD-Signal-Histogram
|
||||||
|
const closes = data.map((x: any) => Number(x.c));
|
||||||
|
const emaFast = ema(closes, fast);
|
||||||
|
const emaSlow = ema(closes, slow);
|
||||||
|
|
||||||
|
// 두 EMA 길이 맞추기 (slow 기준으로 잘라내기)
|
||||||
|
const alignedFast = emaFast.slice(slow - 1);
|
||||||
|
const alignedSlow = emaSlow.slice(slow - 1);
|
||||||
|
|
||||||
|
const macdLine = alignedFast.map((v, i) => v - alignedSlow[i]);
|
||||||
|
|
||||||
|
const signalLine = ema(macdLine, sig);
|
||||||
|
const hist = macdLine.slice(sig - 1)
|
||||||
|
.map((v, i) => v - signalLine[i]);
|
||||||
|
|
||||||
|
return { macdLine, signalLine, hist };
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- 시그널 ----------
|
||||||
|
// const swingSignal = (data: any) => {
|
||||||
|
// const macdHist = macd(data).hist;
|
||||||
|
// const rsi14 = rsi(data, 14);
|
||||||
|
// const bbands = bb(data, 20, 2);
|
||||||
|
//
|
||||||
|
// const offsetMacd = data.length - macdHist.length;
|
||||||
|
// const offsetRsi = data.length - rsi14.length;
|
||||||
|
// const offsetBb = data.length - bbands.lower.length;
|
||||||
|
//
|
||||||
|
// const latest = data.length-1;
|
||||||
|
// const hist = macdHist[latest-offsetMacd];
|
||||||
|
// const prev = macdHist[latest-offsetMacd-1];
|
||||||
|
// const rsiVal = rsi14[latest-offsetRsi];
|
||||||
|
// const close = Number(data[latest].c);
|
||||||
|
// const bbl = bbands.lower[latest-offsetBb];
|
||||||
|
// const bbu = bbands.upper[latest-offsetBb];
|
||||||
|
// console.log(prev, hist, rsiVal, close, bbl)
|
||||||
|
// if(prev<0 && hist>=0 && rsiVal<=35 && close<=bbl) return 'LONG';
|
||||||
|
// if(prev>0 && hist<=0 && rsiVal>=65 && close>=bbu) return 'SHORT';
|
||||||
|
// return 'HOLD';
|
||||||
|
// };
|
||||||
|
|
||||||
|
const swingSignal = (data: any) => {
|
||||||
|
// 1) 기존 지표
|
||||||
|
const macdHist = macd(data).hist;
|
||||||
|
const rsi14 = rsi(data, 14);
|
||||||
|
const bbands = bb(data, 20, 2);
|
||||||
|
|
||||||
|
// 2) ATR 변동성 필터
|
||||||
|
const atr14 = atr(data, 14);
|
||||||
|
const atr63 = atr(data, 63);
|
||||||
|
// const offsetAtr = data.length - atr14.length;
|
||||||
|
const currentATR14 = atr14[atr14.length - 1];
|
||||||
|
const currentATR63 = atr63[atr63.length - 1] || 1;
|
||||||
|
|
||||||
|
// 변동성 급등 조건
|
||||||
|
const volatilityBreak = currentATR14 > currentATR63 * 1.2;
|
||||||
|
|
||||||
|
// 3) 기존 offset 계산
|
||||||
|
const offsetMacd = data.length - macdHist.length;
|
||||||
|
const offsetRsi = data.length - rsi14.length;
|
||||||
|
const offsetBb = data.length - bbands.lower.length;
|
||||||
|
|
||||||
|
const latest = data.length - 1;
|
||||||
|
const hist = macdHist[latest - offsetMacd];
|
||||||
|
const prev = macdHist[latest - offsetMacd - 1];
|
||||||
|
const rsiVal = rsi14[latest - offsetRsi];
|
||||||
|
const close = Number(data[latest].c);
|
||||||
|
const bbl = bbands.lower[latest - offsetBb];
|
||||||
|
const bbu = bbands.upper[latest - offsetBb];
|
||||||
|
|
||||||
|
console.log(volatilityBreak, prev, hist, rsiVal, close, bbl)
|
||||||
|
// 4) 최종 조건
|
||||||
|
if (volatilityBreak &&
|
||||||
|
prev < 0 && hist >= 0 &&
|
||||||
|
rsiVal <= 35 && close <= bbl)
|
||||||
|
return 'LONG';
|
||||||
|
|
||||||
|
if (volatilityBreak &&
|
||||||
|
prev > 0 && hist <= 0 &&
|
||||||
|
rsiVal >= 65 && close >= bbu)
|
||||||
|
return 'SHORT';
|
||||||
|
|
||||||
|
return 'HOLD';
|
||||||
|
};
|
||||||
|
|
||||||
|
export {swingSignal}
|
||||||
4
types/imports.d.ts
vendored
4
types/imports.d.ts
vendored
@@ -10,8 +10,10 @@ declare global {
|
|||||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||||
const calculateBollingerBands: typeof import('../src/utils/meter')['calculateBollingerBands']
|
const calculateBollingerBands: typeof import('../src/utils/meter')['calculateBollingerBands']
|
||||||
|
const calculateCRO: typeof import('../src/utils/meter')['calculateCRO']
|
||||||
const calculateEMA: typeof import('../src/utils/meter')['calculateEMA']
|
const calculateEMA: typeof import('../src/utils/meter')['calculateEMA']
|
||||||
const calculateMACD: typeof import('../src/utils/meter')['calculateMACD']
|
const calculateMACD: typeof import('../src/utils/meter')['calculateMACD']
|
||||||
|
const calculateRSI: typeof import('../src/utils/meter')['calculateRSI']
|
||||||
const computed: typeof import('vue')['computed']
|
const computed: typeof import('vue')['computed']
|
||||||
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
||||||
const computedEager: typeof import('@vueuse/core')['computedEager']
|
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||||
@@ -36,6 +38,7 @@ declare global {
|
|||||||
const defineComponent: typeof import('vue')['defineComponent']
|
const defineComponent: typeof import('vue')['defineComponent']
|
||||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||||
const effectScope: typeof import('vue')['effectScope']
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
|
const entrySignals: typeof import('../src/utils/cro')['entrySignals']
|
||||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||||
const getCandleList: typeof import('../src/utils/api')['getCandleList']
|
const getCandleList: typeof import('../src/utils/api')['getCandleList']
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
@@ -95,6 +98,7 @@ declare global {
|
|||||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||||
const shallowRef: typeof import('vue')['shallowRef']
|
const shallowRef: typeof import('vue')['shallowRef']
|
||||||
|
const swingSignal: typeof import('../src/utils/sig')['swingSignal']
|
||||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||||
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
||||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ export default defineConfig(({ isSsrBuild }) => ({
|
|||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api/v4': {
|
||||||
target: 'http://localhost:7010',
|
target: 'https://fx-api.gateio.ws',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
// rewrite: (path) => path.replace(/^\/api/, ''),
|
// rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user