mirror of
https://git.hmsn.ink/coin/chart.git
synced 2026-03-20 00:02:17 +09:00
368 lines
9.4 KiB
Vue
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>
|