Files
chart/src/App.vue
2025-08-24 22:00:11 +09:00

368 lines
9.4 KiB
Vue

<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import { ref } from 'vue';
/*
* There are example components in both API styles: Options API, and Composition API
*
* Select your preferred style from the imports below:
*/
import LWChart from './components/LWChart.vue';
import {getCandleList} from '/@src/utils/api'
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
* @param {Boolean} ohlc Whether generated dat should include open, high, low, and close values
* @returns {Array} sample data
*/
let socket;
const lazyLock = ref(false)
const positionData = ref('')
const contract = ref('BTC_USDT')
/* Interval : "10s", "1m", "5m", "15m", "30m", "1h", "4h", "8h", "1d", "7d"*/
const time = ref('1h')
const chartOptions = ref({
layout: {
textColor: 'black',
background: { type: 'solid', color: 'white' },
panes: {
separatorColor: '#FF0000',
},
},
height: 1300,
timeScale: {
timeVisible: true, // 분/초까지 표시
secondsVisible: false // 초 숨기고 싶으면 false
},
});
const candleTick = ref([])
const ema = ref([])
const macd = ref({})
const bb = ref({})
const rsi = ref({})
const selectCoins = [
{ title: 'BTC_USDT'},
{ title: 'ETH_USDT'},
{ title: 'SOL_USDT'},
{ title: 'XRP_USDT'},
{ title: 'LINK_USDT'},
]
const selectTimes = [
{ title: '5m'},
{ title: '15m'},
{ title: '30m'},
{ title: '1h'},
{ title: '4h'},
{ title: '8h'},
{ title: '1d'},
{ title: '7d'},
]
const line_color = [
'#AD81FF',
'#FFBAB5',
'#6BFF74',
'#FFFC7B',
'#27FFE6',
'#225EFF'
]
const emaLineOptions = ref({
lineWidth: 2,
color: '#AD81FF',
title: 'EMA',
});
const candleOptions = ref({
upColor: '#26a69a',
downColor: '#ef5350',
borderVisible: false,
wickUpColor: '#26a69a',
wickDownColor: '#ef5350',
});
const macdLineOptions = ref({
lineWidth: 2,
color: '#FFBAB5',
title: 'MACD',
});
const macdSignalOptions = ref({
lineWidth: 2,
color: '#AD81FF',
title: 'SIGNAL',
});
const macdHistogramOptions = ref({
lineWidth: 2,
color: '#27FFE6',
title: 'HISTOGRAM',
});
const upperBbOptions = ref({
color: '#006AFF', // 주황색 계열
lineStyle: 0, // 점선 스타일 (선택)
lineWidth: 2,
title: 'UpperBB',
})
const middleBbOptions = ref({
color: '#006AFF', // 주황색 계열
lineStyle: 1, // 점선 스타일 (선택)
lineWidth: 0,
title: 'MiddleBB(SMA)',
})
const lowerBbOptions = ref({
color: '#006AFF', // 주황색 계열
lineStyle: 0, // 점선 스타일 (선택)
lineWidth: 2,
title: 'LowerBB',
})
const rsiLineOptions = ref({
lineWidth: 2,
color: '#AD81FF',
title: 'RSI',
});
const chartType = ref('candlestick');
const lwChart = ref();
onMounted(() => {
init(contract.value, time.value)
})
const init = (cont, ti) => {
socket = new WebSocket("wss://fx-ws.gateio.ws/v4/ws/usdt");
const dt = new Date()
getCandleList(cont, ti, Math.round(dt.getTime()/1000)).then(data => {
ema.value = calculateEMA(data, 20)
macd.value = calculateMACD(data)
bb.value = calculateBollingerBands(data)
rsi.value = calculateRSI(data)
// console.log(swingSignal(data))
// console.table(entrySignals(data))
candleTick.value = data
})
candleSocket(cont, ti)
}
const distroy = (cont) => {
candleTick.value = []
socket.close()
}
const lazyLoad = async (ti) => {
lazyLock.value = true
getCandleList(contract.value, time.value, ti).then(data => {
candleTick.value = [...data, ...candleTick.value]
ema.value = calculateEMA(data, 20)
macd.value = calculateMACD(data)
bb.value = calculateBollingerBands(data)
rsi.value = calculateRSI(data)
setTimeout(() => {lazyLock.value = false}, 3000)
})
}
const candleSocket = (cont, ti) => {
socket.addEventListener('open', (event) => {
// 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) => {
const msg = JSON.parse(message.data)
if(msg.event === 'update') {
const item = msg.result[0]
const ticks = candleTick.value
if(item.t === ticks[ticks.length - 1].t) {
ticks[ticks.length - 1] = item
candleTick.value = [...ticks]
} else {
ticks.push(item)
candleTick.value = [...ticks]
}
ema.value = calculateEMA(ticks, 20)
macd.value = calculateMACD(ticks)
bb.value = calculateBollingerBands(ticks)
rsi.value = calculateRSI(ticks)
// console.table(entrySignals(ticks))
console.log(swingSignal(ticks))
lazyLock.value = false
}
})
}
const timeToUnix = (time) => {
return Math.floor(new Date(time).getTime() / 1000);
}
const curToUnix = (() => {
return Math.floor(new Date().getTime() / 1000);
})
const epochToString = (epo) => {
const cu = new Date(epo)
return cu.getFullYear() + '-' + cu.getMonth().toString().padStart(2, '0') + '-' +
cu.getDate().toString().padStart(2, '0') + ' ' + cu.getHours().toString().padStart(2, '0') + ':' +
cu.getMinutes().toString().padStart(2, '0') + ':' + cu.getSeconds().toString().padStart(2, '0');
}
const colorsTypeMap = {
area: [
['topColor', 0.4],
['bottomColor', 0],
['lineColor', 1],
],
bar: [
['upColor', 1],
['downColor', 1],
],
baseline: [
['topFillColor1', 0.28],
['topFillColor2', 0.05],
['topLineColor', 1],
['bottomFillColor1', 0.28],
['bottomFillColor2', 0.05],
['bottomLineColor', 1],
],
candlestick: [
['upColor', 1],
['downColor', 1],
['borderUpColor', 1],
['borderDownColor', 1],
['wickUpColor', 1],
['wickDownColor', 1],
],
histogram: [['color', 1]],
line: [['color', 1]],
};
const mouseMove = (data) => {
positionData.value = `
<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 == null ? 0 : data.close}
<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 == null ? 0 : data.high}
<span style="color:red; padding-left:5px; padding-right:3px;">T</span>${data == null ? 0 : epochToString(data.time*1000)}
`;
}
watch(contract, newVal => {
distroy()
init(newVal, time.value)
})
watch(time, newVal => {
distroy()
init(contract.value, newVal)
})
</script>
<template>
<div class="container">
<div class="d-flex justify-content-center">
<v-menu>
<template v-slot:activator="{ props }">
<v-btn color="grey-lighten-5" min-width="100" size="small" v-bind="props">{{contract}}</v-btn>
</template>
<v-list v-model="contract">
<v-list-item
v-for="(item, idx) in selectCoins"
:key="idx"
:value="item.title"
@click="() => {
socket.send(JSON.stringify({'type':'unsubscribe', 'channel': contract, 'time': time}))
contract = item.title
}"
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-menu>
<template v-slot:activator="{ props }">
<v-btn color="grey-lighten-5" min-width="50" size="small" v-bind="props">{{time}}</v-btn>
</template>
<v-list v-model="time">
<v-list-item
v-for="(item, idx) in selectTimes"
:key="idx"
:value="item.title"
@click="() => {
socket.send(JSON.stringify({'type':'unsubscribe', 'channel': contract, 'time': time}))
time = item.title
}"
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
<div class="chart-container" v-if="candleTick !== undefined">
<LWChart
:type="chartType"
:data="candleTick"
:ema="ema"
:macd="macd"
:bb="bb"
:rsi="rsi"
:autosize="true"
:chart-options="chartOptions"
:ema-options="emaLineOptions"
:candle-options="candleOptions"
:macd-line-options="macdLineOptions"
:macd-signal-options="macdSignalOptions"
:macd-histogram-options="macdHistogramOptions"
:lower-bb-options="lowerBbOptions"
:middle-bb-options="middleBbOptions"
:upper-bb-options="upperBbOptions"
:rsi-line-options="rsiLineOptions"
:lazyLock="lazyLock"
@lazyLoad="lazyLoad"
@mouseMove="mouseMove"
ref="lwChart"
/>
<div class="ohlc-info" v-html="positionData"></div>
</div>
</div>
</template>
<style scoped lang="scss">
.chart-container {
height: calc(100% - 3.2em);
}
.ohlc-info {
position: absolute;
top: 20px;
left: 10px;
font-size: 12px;
color: #333;
background: rgba(255, 255, 255, 0.8);
padding: 5px;
border-radius: 4px;
z-index: 1000;
}
</style>