完善知行短线
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import {
|
||||
addWatchlist, clearTrades, getStockPreview, getTrades, getWatchlist as getWatchlistApi,
|
||||
addWatchlist, clearTrades, getStockChips, getStockPreview, getTrades, getWatchlist as getWatchlistApi,
|
||||
importTrades, removeWatchlist,
|
||||
} from '@/api/client';
|
||||
import type {
|
||||
ChartLayoutPrefs, PreviewResponse, ScreenerItemOut, Timeframe, TooltipField,
|
||||
ChartLayoutPrefs, ChipsResponse, PreviewResponse, ScreenerItemOut, Timeframe, TooltipField,
|
||||
TradesImportResponse, UserTrade,
|
||||
} from '@/api/types';
|
||||
import { useSettingsStore, DEFAULT_TOOLTIP_FIELDS, TOOLTIP_FIELDS, type PriceAdjust } from '@/stores/settings';
|
||||
import type { ChipsExt } from '@/indicators';
|
||||
import DetailKLine from './DetailKLine.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -67,6 +68,91 @@ const maPeriods = computed<number[]>(() => layout.value.maPeriods);
|
||||
const subHeights = computed(() => layout.value.subHeights);
|
||||
const tooltipFields = computed<TooltipField[]>(() => layout.value.tooltipFields ?? DEFAULT_TOOLTIP_FIELDS);
|
||||
const showBoll = ref(false);
|
||||
const showZhixing = ref(false);
|
||||
// 知行指标图例行的板块文本(通达信 HYBLOCK+' '+DYBLOCK 的近似;GNBLOCK 概念板块无数据源)
|
||||
const zhixingBlocks = computed(() => {
|
||||
const info = data.value?.info;
|
||||
if (!info) return undefined;
|
||||
return [info.industry, info.area].filter(Boolean).join(' ') || undefined;
|
||||
});
|
||||
|
||||
// ---------- 筹码峰(cyq 截面,跟随 日/周/月K 的当前周期 + 十字线) ----------
|
||||
const showChips = ref(false);
|
||||
const chipsExt = ref<ChipsExt | null>(null);
|
||||
let chipsToken = 0; // 请求序号:切股/切口径瞬间乱序返回的旧截面不能回填
|
||||
let chipsKey = ''; // 已请求去重键(code|adjust|参考日)
|
||||
|
||||
function toChipsExt(res: ChipsResponse, refClose: number): ChipsExt {
|
||||
return {
|
||||
rows: res.rows.map((r) => ({ price: r.price, percent: r.percent })),
|
||||
date: res.trade_date ?? undefined,
|
||||
refClose,
|
||||
winner: res.winner_rate,
|
||||
avg: res.weight_avg,
|
||||
costLow: res.cost_5pct,
|
||||
costHigh: res.cost_95pct,
|
||||
error: res.error ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/** 筹码锚点(同花顺式):返回 参考日 + 锚定 bar 收盘价(获利/套牢的分界价)。
|
||||
* 日K=bar 当日;周/月/年K 的 bar ts 是周期首日,参考日=下一根 bar 首日的前一天
|
||||
* (最后一根=今天,后端再吸附到 <=参考日 的最近有数据交易日)。
|
||||
* idx 缺省=窗口最后一根(跟随当前可视周期);十字线跟随时传落点下标。
|
||||
* 注:翻页前插的更早 bar 不在 data.candles 里,十字线落到那些 bar 上时沿用上一截面。 */
|
||||
function chipsAnchor(idx?: number): { ref: string; close: number } | undefined {
|
||||
const bars = data.value?.candles;
|
||||
if (!bars?.length) return undefined;
|
||||
const i = idx != null && idx >= 0 && idx < bars.length ? idx : bars.length - 1;
|
||||
if (timeframe.value === '1d') return { ref: bars[i].ts.slice(0, 10), close: bars[i].close };
|
||||
const nextTs = i + 1 < bars.length ? new Date(bars[i + 1].ts).getTime() - 86400000 : Date.now();
|
||||
return { ref: fmtDashDate(Math.min(nextTs, Date.now())), close: bars[i].close };
|
||||
}
|
||||
|
||||
async function loadChips(anchorIdx?: number) {
|
||||
if (!showChips.value) return;
|
||||
const anchor = chipsAnchor(anchorIdx);
|
||||
if (!anchor) return;
|
||||
const key = `${active.value}|${adjust.value}|${anchor.ref}`;
|
||||
if (key === chipsKey) return; // 同股同口径同参考日不重复请求(后端有 Redis 缓存,前端也省一次往返)
|
||||
chipsKey = key;
|
||||
const token = ++chipsToken;
|
||||
try {
|
||||
const res = await getStockChips(active.value, { date: anchor.ref, adjust: adjust.value });
|
||||
if (token === chipsToken) chipsExt.value = toChipsExt(res, anchor.close);
|
||||
} catch {
|
||||
if (token === chipsToken) chipsExt.value = { rows: [], refClose: anchor.close, error: '筹码数据获取失败' };
|
||||
}
|
||||
}
|
||||
|
||||
/** K线数据就绪/切换(切股、复权、周期、日期跳转都经 load() 换 data):筹码重置到新窗口末根 */
|
||||
watch(data, () => {
|
||||
chipsKey = '';
|
||||
chipHoverTs = null;
|
||||
chipsExt.value = null;
|
||||
void loadChips();
|
||||
});
|
||||
|
||||
/** 十字线跟随(防抖 350ms):落在哪根K线就取那个周期的截面;离开图表保留最后截面(通达信同款) */
|
||||
let chipHoverTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let chipHoverTs: number | null = null;
|
||||
function onCrosshairDate(ts: number) {
|
||||
if (!showChips.value || chipHoverTs === ts) return;
|
||||
chipHoverTs = ts;
|
||||
if (chipHoverTimer) clearTimeout(chipHoverTimer);
|
||||
chipHoverTimer = setTimeout(() => {
|
||||
chipHoverTimer = null;
|
||||
const bars = data.value?.candles;
|
||||
if (!bars) return;
|
||||
const idx = bars.findIndex((b) => new Date(b.ts).getTime() === ts);
|
||||
if (idx >= 0) void loadChips(idx);
|
||||
}, 350);
|
||||
}
|
||||
|
||||
function toggleChips() {
|
||||
showChips.value = !showChips.value;
|
||||
if (showChips.value) { chipsKey = ''; chipHoverTs = null; void loadChips(); }
|
||||
}
|
||||
|
||||
function toggleSub(key: string) {
|
||||
const cur = subPanes.value;
|
||||
@@ -580,6 +666,22 @@ const fmtListDate = (s?: string | null) => (s && s.length === 8 ? `${s.slice(0,
|
||||
title="主图叠加布林带"
|
||||
@click="showBoll = !showBoll"
|
||||
>BOLL</button>
|
||||
<!-- 知行:通达信导入的自定义主图指标(趋势线+多空线) -->
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border px-2.5 py-1 text-[13px] transition-colors"
|
||||
:class="showZhixing ? 'border-[#E61851] bg-[#E61851] text-white' : 'border-[#26272E] bg-[#101014] text-[#9BA3AE]'"
|
||||
title="知行指标:短期趋势线 EMA(EMA(C,10),10) + 多空线 (MA14+MA28+MA57+MA114)/4"
|
||||
@click="showZhixing = !showZhixing"
|
||||
>知行</button>
|
||||
<!-- 筹码峰:右侧横向线状直方图(选中K线下方获利盘红/上方套牢盘蓝+平均成本虚线) -->
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border px-2.5 py-1 text-[13px] transition-colors"
|
||||
:class="showChips ? 'border-cyan-400 bg-cyan-400 text-black' : 'border-[#26272E] bg-[#101014] text-[#9BA3AE]'"
|
||||
title="筹码峰:各价位持仓占比(选中K线分界:下方获利盘红/上方套牢盘蓝),随日/周/月K与十字线切换截面"
|
||||
@click="toggleChips"
|
||||
>筹码峰</button>
|
||||
<!-- 实盘交易点:交割单导入的买卖标记 -->
|
||||
<button
|
||||
type="button"
|
||||
@@ -708,6 +810,11 @@ const fmtListDate = (s?: string | null) => (s && s.length === 8 ? `${s.slice(0,
|
||||
:ma-periods="maPeriods"
|
||||
:sub-heights="subHeights"
|
||||
:show-boll="showBoll"
|
||||
:show-zhixing="showZhixing"
|
||||
:zhixing-blocks="zhixingBlocks"
|
||||
:show-chips="showChips"
|
||||
:chips="chipsExt"
|
||||
@crosshair-date="onCrosshairDate"
|
||||
:timeframe="timeframe"
|
||||
:tooltip-fields="tooltipFields"
|
||||
:center-ts="jumpTs"
|
||||
|
||||
Reference in New Issue
Block a user