벡테스팅 수정
This commit is contained in:
@@ -307,6 +307,8 @@ export class ChartManager {
|
||||
|
||||
/** 차트 하단 거래량 pane 표시 */
|
||||
private _volumeVisible = true;
|
||||
/** false — 거래량 시리즈·pane 1 미생성 (가상매매 카드 등) */
|
||||
private _volumePaneEnabled = true;
|
||||
|
||||
/** pane 간 구분선 (설정 화면) */
|
||||
private _paneSeparatorOptions: ChartPaneSeparatorOptions =
|
||||
@@ -324,10 +326,12 @@ export class ChartManager {
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
theme: Theme,
|
||||
options?: { autoSize?: boolean; compactPaneLayout?: boolean },
|
||||
options?: { autoSize?: boolean; compactPaneLayout?: boolean; volumePaneEnabled?: boolean },
|
||||
) {
|
||||
this.container = container;
|
||||
this._compactPaneLayout = options?.compactPaneLayout ?? false;
|
||||
this._volumePaneEnabled = options?.volumePaneEnabled ?? true;
|
||||
if (!this._volumePaneEnabled) this._volumeVisible = false;
|
||||
const autoSize = options?.autoSize ?? !this._compactPaneLayout;
|
||||
const t = getTheme(theme);
|
||||
this.chart = createChart(container, {
|
||||
@@ -454,28 +458,31 @@ export class ChartManager {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(this.mainSeries as ISeriesApi<any>).setData(mainData);
|
||||
|
||||
// Volume sub-pane (pane index 1)
|
||||
this.volumeSeries = this.chart.addSeries(HistogramSeries, {
|
||||
priceFormat: { type: 'volume' },
|
||||
priceScaleId: 'volume',
|
||||
}, 1);
|
||||
|
||||
// 초기 volume pane 크기: resetPaneHeights() 호출 전 임시 설정
|
||||
// (setHeight 대신 setStretchFactor - LWC 내부 기준 높이와 무관하게 동작)
|
||||
const panesInit = this.chart.panes();
|
||||
if (panesInit[0]) panesInit[0].setStretchFactor(0.88);
|
||||
if (panesInit[1]) panesInit[1].setStretchFactor(0.12);
|
||||
if (this._volumePaneEnabled) {
|
||||
// Volume sub-pane (pane index 1)
|
||||
this.volumeSeries = this.chart.addSeries(HistogramSeries, {
|
||||
priceFormat: { type: 'volume' },
|
||||
priceScaleId: 'volume',
|
||||
}, 1);
|
||||
|
||||
this.volumeSeries.setData(bars.map(b => ({
|
||||
time: b.time as Time,
|
||||
value: b.volume,
|
||||
color: b.close >= b.open ? `${t.upColor}88` : `${t.downColor}88`,
|
||||
})));
|
||||
// 초기 volume pane 크기: resetPaneHeights() 호출 전 임시 설정
|
||||
if (panesInit[0]) panesInit[0].setStretchFactor(0.88);
|
||||
if (panesInit[1]) panesInit[1].setStretchFactor(0.12);
|
||||
|
||||
if (!this._volumeVisible) {
|
||||
try {
|
||||
this.volumeSeries.applyOptions({ visible: false } as Parameters<ISeriesApi<SeriesType>['applyOptions']>[0]);
|
||||
} catch { /* ok */ }
|
||||
this.volumeSeries.setData(bars.map(b => ({
|
||||
time: b.time as Time,
|
||||
value: b.volume,
|
||||
color: b.close >= b.open ? `${t.upColor}88` : `${t.downColor}88`,
|
||||
})));
|
||||
|
||||
if (!this._volumeVisible) {
|
||||
try {
|
||||
this.volumeSeries.applyOptions({ visible: false } as Parameters<ISeriesApi<SeriesType>['applyOptions']>[0]);
|
||||
} catch { /* ok */ }
|
||||
}
|
||||
} else if (panesInit[0]) {
|
||||
panesInit[0].setStretchFactor(1);
|
||||
}
|
||||
|
||||
this._reapplyAllPatternMarkers();
|
||||
@@ -2621,6 +2628,10 @@ export class ChartManager {
|
||||
|
||||
/** 차트 설정: 거래량 pane 표시 on/off */
|
||||
setVolumeVisible(visible: boolean, availableHeight?: number): void {
|
||||
if (!this._volumePaneEnabled) {
|
||||
this._volumeVisible = false;
|
||||
return;
|
||||
}
|
||||
this._volumeVisible = visible;
|
||||
if (this._candleOnlyLayout) return;
|
||||
this.applyVolumeVisibility(visible, availableHeight);
|
||||
@@ -3954,7 +3965,7 @@ export class ChartManager {
|
||||
}
|
||||
|
||||
private _volumeFrac(H: number): number {
|
||||
if (!this._volumeVisible || this._candleOnlyLayout) return 0;
|
||||
if (!this._volumePaneEnabled || !this._volumeVisible || this._candleOnlyLayout) return 0;
|
||||
return Math.min(Math.max(60 / H, 0.04), 0.12);
|
||||
}
|
||||
|
||||
@@ -3970,7 +3981,7 @@ export class ChartManager {
|
||||
? this._lastLayoutAvailableHeight
|
||||
: this.container.clientHeight);
|
||||
|
||||
const volumeShown = this._volumeVisible && !this._candleOnlyLayout;
|
||||
const volumeShown = this._volumePaneEnabled && this._volumeVisible && !this._candleOnlyLayout;
|
||||
const VOL_FRAC = this._volumeFrac(H);
|
||||
const MIN_IND_PX = this._minIndPx(H);
|
||||
const ORPHAN_STRETCH = 0.0001;
|
||||
|
||||
@@ -44,8 +44,8 @@ export async function addVirtualTarget(
|
||||
const en = meta.englishName ?? market.replace(/^KRW-/, '');
|
||||
const item: VirtualTargetItem = {
|
||||
market,
|
||||
/** null = 상단 기본 전략 사용 */
|
||||
strategyId: null,
|
||||
/** 신규 추가 시점의 상단 기본전략(템플릿)을 종목에 고정 */
|
||||
strategyId: session.globalStrategyId,
|
||||
koreanName: meta.koreanName,
|
||||
englishName: en,
|
||||
};
|
||||
|
||||
@@ -33,3 +33,15 @@ export function formatVirtualTargetOptionLabel(
|
||||
const { koreanName: ko, englishName: en } = resolveVirtualTargetNames(market, koreanName, englishName);
|
||||
return `${ko}(${en})`;
|
||||
}
|
||||
|
||||
/** 카드·차트 상단 — 투자전략 표시명 */
|
||||
export function resolveVirtualTargetStrategyDisplayName(
|
||||
strategy: { name?: string } | undefined,
|
||||
strategies: Array<{ id?: number; name?: string }>,
|
||||
readOnlyStrategyLabel?: string,
|
||||
): string {
|
||||
const ro = readOnlyStrategyLabel?.trim();
|
||||
if (ro) return ro;
|
||||
if (strategy?.name?.trim()) return strategy.name.trim();
|
||||
return '전략 미지정';
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ import type { VirtualTargetItem } from './virtualTradingStorage';
|
||||
/** 종목 전략 셀렉트 — 기본(전역) 전략 옵션 값 */
|
||||
export const VIRTUAL_DEFAULT_STRATEGY_VALUE = '__default__';
|
||||
|
||||
/** 종목에 전략이 지정되지 않았으면 전역(기본) 전략 ID */
|
||||
/**
|
||||
* 종목별 실효 전략 ID.
|
||||
* strategyId 가 있으면 그대로 사용하고, null 이면 globalStrategyId(레거시·미설정)만 참조.
|
||||
* 상단 기본전략 변경은 기존 null 종목을 freezeTargetsBeforeGlobalStrategyChange 로 고정한 뒤 session 만 갱신.
|
||||
*/
|
||||
export function resolveVirtualTargetStrategyId(
|
||||
target: Pick<VirtualTargetItem, 'strategyId'>,
|
||||
globalStrategyId: number | null,
|
||||
@@ -12,6 +16,21 @@ export function resolveVirtualTargetStrategyId(
|
||||
return globalStrategyId;
|
||||
}
|
||||
|
||||
/** 기본전략 변경 직전 — null(기본 따름) 종목을 이전 기본전략 ID 로 고정 */
|
||||
export function freezeTargetsBeforeGlobalStrategyChange(
|
||||
targets: VirtualTargetItem[],
|
||||
previousGlobalStrategyId: number | null,
|
||||
): VirtualTargetItem[] {
|
||||
if (previousGlobalStrategyId == null) return targets;
|
||||
let changed = false;
|
||||
const next = targets.map(t => {
|
||||
if (t.strategyId != null) return t;
|
||||
changed = true;
|
||||
return { ...t, strategyId: previousGlobalStrategyId };
|
||||
});
|
||||
return changed ? next : targets;
|
||||
}
|
||||
|
||||
export function usesDefaultStrategy(target: Pick<VirtualTargetItem, 'strategyId'>): boolean {
|
||||
return target.strategyId == null;
|
||||
}
|
||||
@@ -26,6 +45,7 @@ export function parseTargetStrategySelectValue(value: string): number | null {
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
/** 종목 셀렉트 — 기본 전략(전역) 옵션 라벨 (신규 추가 시 적용되는 템플릿) */
|
||||
export function defaultStrategyOptionLabel(
|
||||
globalStrategyId: number | null,
|
||||
strategies: Array<{ id?: number; name?: string }>,
|
||||
@@ -35,21 +55,3 @@ export function defaultStrategyOptionLabel(
|
||||
return name ? `기본 전략 (${name})` : '기본 전략';
|
||||
}
|
||||
|
||||
/**
|
||||
* 예전에 전역 전략 ID가 종목에 복사 저장된 항목 → null(기본 전략 따름)로 정규화
|
||||
*/
|
||||
export function coalesceTargetsToDefaultStrategy(
|
||||
targets: VirtualTargetItem[],
|
||||
globalStrategyId: number | null,
|
||||
): VirtualTargetItem[] {
|
||||
if (globalStrategyId == null) return targets;
|
||||
let changed = false;
|
||||
const next = targets.map(t => {
|
||||
if (t.strategyId != null && t.strategyId === globalStrategyId) {
|
||||
changed = true;
|
||||
return { ...t, strategyId: null };
|
||||
}
|
||||
return t;
|
||||
});
|
||||
return changed ? next : targets;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user