You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
445 lines
12 KiB
445 lines
12 KiB
<template>
|
|
<div ref="qxnlzhqEchartsRef" id="qxnlzhqEcharts"></div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, onBeforeUnmount, toRef, reactive } from "vue";
|
|
import * as echarts from "echarts";
|
|
|
|
defineExpose({ initQXNLZHEcharts });
|
|
|
|
let qxnlzhqEchartsRef = ref(null);
|
|
let qxnlzhqEchartsInstance = null;
|
|
|
|
let regions = reactive([]);
|
|
|
|
// 设置区域名称 位置
|
|
function getNameTop(min, max, regionMiidle) {
|
|
// 获取整个图表的高度
|
|
const chartHeight = qxnlzhqEchartsInstance.getHeight();
|
|
// 60: 为x轴占的高度
|
|
return (max - Number(regionMiidle)) / (max - min) * (chartHeight - 60)
|
|
}
|
|
// 设置区域最大值 位置
|
|
function getNumberTop(min, max, regionMax) {
|
|
// 获取整个图表的高度
|
|
const chartHeight = qxnlzhqEchartsInstance.getHeight();
|
|
// 60: 为x轴占的高度
|
|
return (max - Number(regionMax)) / (max - min) * (chartHeight - 60)
|
|
}
|
|
|
|
// 生成图形标注(核心逻辑)
|
|
const generateGraphics = (min, max) => {
|
|
return regions.flatMap((region) => {
|
|
const middleY = (Number(region.min) + Number(region.max)) / 2;
|
|
return [
|
|
// 区域名称(中间位置)
|
|
{
|
|
type: "text",
|
|
left: '17', // 靠近y轴内侧
|
|
top: getNameTop(min, max, middleY),
|
|
style: {
|
|
text: region.name,
|
|
fill: region.fontColor,
|
|
fontSize: 14,
|
|
fontWeight: "bold",
|
|
},
|
|
z: 3,
|
|
},
|
|
// y轴数值(顶部位置)
|
|
{
|
|
type: "text",
|
|
left: '25',
|
|
top: getNumberTop(min, max, region.max),
|
|
// top: 100,
|
|
style: {
|
|
text: region.max.toString(),
|
|
fill: region.NumberColor,
|
|
fontSize: 12,
|
|
},
|
|
z: 3,
|
|
},
|
|
];
|
|
});
|
|
};
|
|
|
|
function initQXNLZHEcharts(kline, qxnlzhqData) {
|
|
// 测试数据 !!! 删掉
|
|
// qxnlzhqData.topxh = ["2025/04/04", "2025/04/15"]
|
|
// qxnlzhqData.lowxh = ["2025/04/08", "2025/04/18"]
|
|
// qxnlzhqData.qixh = ["2025/04/10", "2025/04/21"]
|
|
if (qxnlzhqEchartsInstance) {
|
|
qxnlzhqEchartsInstance.dispose();
|
|
}
|
|
// 数据
|
|
let mixData = [];
|
|
kline.forEach((element) => {
|
|
let date = element[0];
|
|
let value = [element[1], element[2], element[3], element[4]];
|
|
mixData.push({
|
|
date,
|
|
value,
|
|
});
|
|
});
|
|
|
|
// 动态区域配置
|
|
// dd到zc 低吸区------情绪冰点区 ; zc到ht 关注区------认知潜伏区; ht到qs 回调区------多空消化区 ; qs到tp 拉升区------共识加速区;
|
|
// tp到js 突破区------情绪临界区 ; js到yl 警示区-------杠杆失衡区 ; yl到gg 风险区-------情绪熔断区;
|
|
regions = [
|
|
{
|
|
min: qxnlzhqData.dd,
|
|
max: qxnlzhqData.zc,
|
|
name: "【情绪冰点区】",
|
|
color: "#e7a5d6",
|
|
fontColor: '#000',
|
|
NumberColor: 'blue',
|
|
},
|
|
{
|
|
min: qxnlzhqData.zc,
|
|
max: qxnlzhqData.ht,
|
|
name: "【认知潜伏区】",
|
|
color: "#f36587",
|
|
fontColor: '#000',
|
|
NumberColor: 'blue',
|
|
},
|
|
{
|
|
min: qxnlzhqData.ht,
|
|
max: qxnlzhqData.qs,
|
|
name: "【多空消化区】",
|
|
color: "#e99883",
|
|
fontColor: '#000',
|
|
NumberColor: 'blue',
|
|
},
|
|
{
|
|
min: qxnlzhqData.qs,
|
|
max: qxnlzhqData.tp,
|
|
name: "【共识加速区】",
|
|
color: "#f0db84",
|
|
fontColor: '#000',
|
|
NumberColor: 'red',
|
|
},
|
|
{
|
|
min: qxnlzhqData.tp,
|
|
max: qxnlzhqData.js,
|
|
name: "【情绪临界区】",
|
|
color: "#dbeee3",
|
|
fontColor: 'red',
|
|
NumberColor: 'red',
|
|
},
|
|
];
|
|
// gg yl为-1 不绘制部分图表
|
|
if (Number(qxnlzhqData.yl) != -1) {
|
|
regions.push(
|
|
{
|
|
min: qxnlzhqData.js,
|
|
max: qxnlzhqData.yl,
|
|
name: "【杠杆失衡区】",
|
|
color: "#9ac2d8",
|
|
fontColor: 'red',
|
|
NumberColor: 'red',
|
|
},
|
|
)
|
|
}
|
|
if (Number(qxnlzhqData.gg) != -1) {
|
|
regions.push(
|
|
{
|
|
min: qxnlzhqData.yl,
|
|
max: qxnlzhqData.gg,
|
|
name: "【情绪熔断区】",
|
|
color: "#bce283",
|
|
fontColor: 'red',
|
|
NumberColor: 'red',
|
|
},
|
|
)
|
|
}
|
|
|
|
// 计算止盈止损价格
|
|
const stopProfitPrice = Number(qxnlzhqData.cc) * 1.05; // 止盈价
|
|
const stopLossPrice = Number(qxnlzhqData.cc) * 0.97; // 止损价
|
|
// 确定起始和结束位置
|
|
const startIndex = Math.max(0, mixData.length - 17);
|
|
// 创建完整数据数组
|
|
const takeProfitData = new Array(mixData.length).fill(null);
|
|
const stopLossData = new Array(mixData.length).fill(null);
|
|
// 填充显示区域的数据
|
|
for (var i = startIndex; i < mixData.length; i++) {
|
|
takeProfitData[i] = stopProfitPrice;
|
|
stopLossData[i] = stopLossPrice;
|
|
}
|
|
|
|
// topxh、lowxh、qixh 对应k线染色
|
|
// 创建中间区域数据
|
|
const middleRangeData = [];
|
|
const middleRangeData1 = [];
|
|
const markPointData = [];
|
|
mixData.forEach((item, index) => {
|
|
const [open, close, low, high] = item.value;
|
|
const rangeHeight = high - low;
|
|
// const middleThirdStart = low + rangeHeight * (1/3);
|
|
// const middleThirdEnd = low + rangeHeight * (2/3);
|
|
|
|
let color = null;
|
|
|
|
if (qxnlzhqData.topxh.includes(item.date)) {
|
|
color = '#000000'; // 黑色
|
|
} else if (qxnlzhqData.lowxh.includes(item.date)) {
|
|
color = '#1E90FF'; // 蓝色
|
|
}
|
|
|
|
|
|
// 添加中间区域数据
|
|
if (color) {
|
|
middleRangeData.push({
|
|
value: [index, close > open ? (close - open) : (open - close)], // 修正数据格式
|
|
itemStyle: {
|
|
normal: {
|
|
color: color
|
|
}
|
|
},
|
|
});
|
|
middleRangeData1.push({
|
|
value: [index, close > open ? open : close], // 修正数据格式
|
|
itemStyle: {
|
|
normal: {
|
|
color: 'transparent'
|
|
}
|
|
},
|
|
});
|
|
} else {
|
|
middleRangeData.push(null);
|
|
middleRangeData1.push(null);
|
|
}
|
|
|
|
// 添加文字标记数据
|
|
if (qxnlzhqData.qixh.includes(item.date)) {
|
|
markPointData.push({
|
|
name: '起',
|
|
coord: [index, (open + close) / 2],
|
|
itemStyle: {
|
|
normal: {
|
|
color: 'rgba(0,0,0,0)' // 标记点透明
|
|
}
|
|
},
|
|
label: {
|
|
normal:{
|
|
show: true,
|
|
position: 'inside',
|
|
formatter: '起',
|
|
textStyle: {
|
|
color: '#FF0000',
|
|
fontSize: 10,
|
|
// fontWeight: 'bold'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// 初始化图表
|
|
qxnlzhqEchartsInstance = echarts.init(qxnlzhqEchartsRef.value);
|
|
let option;
|
|
// 设置图表配置
|
|
option = {
|
|
xAxis: {
|
|
type: "category",
|
|
data: mixData.map((item) => item.date),
|
|
axisLabel: {
|
|
rotate: 45, // 日期旋转45度防止重叠
|
|
},
|
|
},
|
|
yAxis: {
|
|
scale: true,
|
|
splitLine: {
|
|
show: false,
|
|
},
|
|
axisLabel: { // 刻度标签
|
|
show: false // 不显示刻度标签
|
|
},
|
|
axisTick: { // 刻度线
|
|
show: false // 不显示刻度线
|
|
},
|
|
min:
|
|
qxnlzhqData.dd < stopLossPrice * 0.98
|
|
? Math.floor(qxnlzhqData.dd)
|
|
: Math.floor(stopLossPrice * 0.98),
|
|
max:
|
|
qxnlzhqData.yl > stopProfitPrice * 1.02
|
|
? Math.ceil(qxnlzhqData.yl)
|
|
: Math.ceil(stopProfitPrice * 1.02),
|
|
},
|
|
// 自定义区域名称和区域范围值 位置
|
|
graphic: generateGraphics(qxnlzhqData.dd < stopLossPrice * 0.98
|
|
? Math.floor(qxnlzhqData.dd)
|
|
: Math.floor(stopLossPrice * 0.98),qxnlzhqData.yl > stopProfitPrice * 1.02
|
|
? Math.ceil(qxnlzhqData.yl)
|
|
: Math.ceil(stopProfitPrice * 1.02)),
|
|
series: [
|
|
{
|
|
type: "candlestick",
|
|
data: mixData.map((item) => item.value),
|
|
z: 1,
|
|
markPoint: {
|
|
symbol: 'circle',
|
|
symbolSize: 10,
|
|
data: markPointData,
|
|
z: 5 // 确保标记显示在最上层
|
|
},
|
|
itemStyle: {
|
|
normal: {
|
|
// 阳线样式(收盘 > 开盘)
|
|
color: 'transparent', // 阳线色
|
|
color0: '#008080', // 阴线色
|
|
borderColor: '#ff0783', // 阳线边框色(红)
|
|
borderColor0: '#008080', // 阴线边框色(绿)
|
|
}
|
|
},
|
|
// 实现 分区域背景色
|
|
markArea: {
|
|
silent: true,
|
|
data: regions.map((region) => [
|
|
{
|
|
yAxis: region.min,
|
|
itemStyle: { normal: { color: region.color } },
|
|
},
|
|
{ yAxis: region.max },
|
|
]),
|
|
},
|
|
},
|
|
{
|
|
name: '中间区域',
|
|
type: 'bar',
|
|
stack: 'total',
|
|
data: middleRangeData1,
|
|
barWidth: '20%',
|
|
barCategoryGap: '40%',
|
|
itemStyle: {
|
|
normal: {
|
|
color: 'rgba(0,0,0,0)' // 默认透明
|
|
}
|
|
},
|
|
z:2
|
|
},
|
|
// 中间区域染色
|
|
{
|
|
name: '中间区域',
|
|
type: 'bar',
|
|
stack: 'total',
|
|
data: middleRangeData,
|
|
barWidth: '20%',
|
|
barCategoryGap: '40%',
|
|
itemStyle: {
|
|
normal: {
|
|
color: 'rgba(0,0,0,0)' // 默认透明
|
|
}
|
|
},
|
|
z:2
|
|
},
|
|
{
|
|
name: '止盈线',
|
|
type: 'line',
|
|
data: takeProfitData,
|
|
symbol: 'none',
|
|
lineStyle: {
|
|
normal: {
|
|
color: '#ff80ff', // 蓝色
|
|
width: 2,
|
|
type: 'solid'
|
|
}
|
|
},
|
|
markPoint: {
|
|
symbol: 'circle',
|
|
symbolSize: 1,
|
|
data: [
|
|
{
|
|
coord: [mixData.map((item) => item.value).length - 1, stopProfitPrice],
|
|
itemStyle: {
|
|
color: '#ff80ff'
|
|
},
|
|
label: {
|
|
normal: {
|
|
show: true,
|
|
position: 'bottom',
|
|
formatter: `{text|止盈: ${stopProfitPrice}}`,
|
|
rich: {
|
|
text: {
|
|
color: '#820a06',
|
|
fontSize: 14,
|
|
fontWeight: 'bold'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
name: '止损线',
|
|
type: 'line',
|
|
data: stopLossData,
|
|
symbol: 'none',
|
|
lineStyle: {
|
|
normal: {
|
|
color: '#080bfd', // 红色
|
|
width: 2,
|
|
type: 'solid'
|
|
}
|
|
},
|
|
markPoint: {
|
|
symbol: 'circle',
|
|
symbolSize: 1,
|
|
data: [
|
|
{
|
|
coord: [mixData.map((item) => item.value).length - 1, stopLossPrice],
|
|
itemStyle: {
|
|
color: '#080bfd'
|
|
},
|
|
label: {
|
|
normal: {
|
|
show: true,
|
|
position: 'bottom',
|
|
formatter: `{text|止损: ${stopLossPrice}}`,
|
|
rich: {
|
|
text: {
|
|
color: '#080bfd',
|
|
fontSize: 14,
|
|
fontWeight: 'bold'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
},
|
|
],
|
|
grid: {
|
|
left: "0",
|
|
right: "10",
|
|
top: '10',
|
|
bottom: "0",
|
|
containLabel: true,
|
|
},
|
|
};
|
|
// 应用配置
|
|
qxnlzhqEchartsInstance.setOption(option);
|
|
// 监听窗口大小变化,调整图表尺寸
|
|
window.addEventListener('resize', () => {
|
|
qxnlzhqEchartsInstance.resize()
|
|
})
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
// 组件卸载时销毁图表
|
|
if (qxnlzhqEchartsInstance) {
|
|
qxnlzhqEchartsInstance.dispose();
|
|
}
|
|
});
|
|
</script>
|
|
<style scoped>
|
|
#qxnlzhqEcharts {
|
|
width: 100%;
|
|
height: 700px;
|
|
}
|
|
</style>
|