goldenChat base source add

This commit is contained in:
aidev
2026-05-23 15:11:48 +09:00
commit a4ea7762b5
2081 changed files with 1155760 additions and 0 deletions
+310
View File
@@ -0,0 +1,310 @@
/**
* 추세 강도(정배열/역배열) 계산 유틸리티
*
* 정배열: 단기 이동평균 > 중기 이동평균 > 장기 이동평균 (상승 추세)
* 역배열: 단기 이동평균 < 중기 이동평균 < 장기 이동평균 (하락 추세)
*
* 공식:
* S = ((MA10 - MA60) + (MA20 - MA60)) / ATR(14)
* TrendStrength = tanh(S) * 100
*
* 결과: -100 ~ +100
* - +100에 가까움: 강한 정배열(상승추세)
* - -100에 가까움: 강한 역배열(하락추세)
* - 0 근처: 추세 약함 또는 혼조
*/
// OHLC 캔들 데이터 인터페이스
export interface OHLCData {
open: number;
high: number;
low: number;
close: number;
volume?: number;
timestamp?: string;
}
// 추세 강도 결과 인터페이스
export interface TrendStrengthResult {
symbol: string;
koreanName: string;
englishName?: string;
trendStrength: number; // -100 ~ +100
ma10: number;
ma20: number;
ma60: number;
atr14: number;
isFavorite?: boolean;
}
/**
* 단순 이동평균(SMA) 계산
* @param data 종가 배열
* @param period 이동평균 기간
* @returns 이동평균 값 (데이터가 부족하면 null)
*/
export function calculateSMA(data: number[], period: number): number | null {
if (data.length < period) return null;
const recentData = data.slice(-period);
const sum = recentData.reduce((acc, val) => acc + val, 0);
return sum / period;
}
/**
* ATR(Average True Range) 계산
* @param ohlcData OHLC 데이터 배열
* @param period ATR 기간 (기본: 14)
* @returns ATR 값 (데이터가 부족하면 null)
*/
export function calculateATR(ohlcData: OHLCData[], period: number = 14): number | null {
if (ohlcData.length < period + 1) return null;
const trueRanges: number[] = [];
// True Range 계산 (최근 period+1개 데이터 사용)
const recentData = ohlcData.slice(-(period + 1));
for (let i = 1; i < recentData.length; i++) {
const current = recentData[i];
const previous = recentData[i - 1];
// True Range = max(High - Low, |High - Previous Close|, |Low - Previous Close|)
const tr = Math.max(
current.high - current.low,
Math.abs(current.high - previous.close),
Math.abs(current.low - previous.close)
);
trueRanges.push(tr);
}
// ATR = SMA of True Ranges
const atr = trueRanges.reduce((acc, val) => acc + val, 0) / trueRanges.length;
return atr;
}
/**
* 추세 강도 계산
*
* 공식:
* S = ((MA10 - MA60) + (MA20 - MA60)) / ATR(14)
* TrendStrength = tanh(S) * 100
*
* @param ohlcData OHLC 데이터 배열 (최소 60개 이상 필요)
* @returns 추세 강도 (-100 ~ +100) 또는 null
*/
export function calculateTrendStrength(ohlcData: OHLCData[]): {
trendStrength: number;
ma10: number;
ma20: number;
ma60: number;
atr14: number;
} | null {
// 최소 60개 데이터 필요
if (ohlcData.length < 60) {
console.warn('추세 강도 계산에 최소 60개 데이터 필요');
return null;
}
// 종가 배열 추출
const closePrices = ohlcData.map(d => d.close);
// 이동평균 계산
const ma10 = calculateSMA(closePrices, 10);
const ma20 = calculateSMA(closePrices, 20);
const ma60 = calculateSMA(closePrices, 60);
// ATR 계산
const atr14 = calculateATR(ohlcData, 14);
// 유효성 검사
if (ma10 === null || ma20 === null || ma60 === null || atr14 === null || atr14 === 0) {
return null;
}
// S 계산: ((MA10 - MA60) + (MA20 - MA60)) / ATR(14)
const s = ((ma10 - ma60) + (ma20 - ma60)) / atr14;
// TrendStrength = tanh(S) * 100
const trendStrength = Math.tanh(s) * 100;
return {
trendStrength: Math.round(trendStrength * 100) / 100, // 소수점 2자리
ma10: Math.round(ma10 * 100) / 100,
ma20: Math.round(ma20 * 100) / 100,
ma60: Math.round(ma60 * 100) / 100,
atr14: Math.round(atr14 * 100) / 100,
};
}
/**
* 추세 강도에 따른 라벨 반환
* @param strength 추세 강도 값
* @returns 라벨 문자열
*/
export function getTrendLabel(strength: number): string {
if (strength >= 80) return '매우 강한 정배열';
if (strength >= 50) return '강한 정배열';
if (strength >= 20) return '정배열';
if (strength >= -20) return '중립';
if (strength >= -50) return '역배열';
if (strength >= -80) return '강한 역배열';
return '매우 강한 역배열';
}
/**
* 정배열 강도 계산 (0 ~ 100)
* trendStrength가 양수일 때만 값이 있음
* @param trendStrength 통합 추세 강도 (-100 ~ +100)
* @returns 정배열 강도 (0 ~ 100)
*/
export function getBullishStrength(trendStrength: number): number {
return Math.max(0, trendStrength);
}
/**
* 역배열 강도 계산 (0 ~ 100)
* trendStrength가 음수일 때만 값이 있음
* @param trendStrength 통합 추세 강도 (-100 ~ +100)
* @returns 역배열 강도 (0 ~ 100)
*/
export function getBearishStrength(trendStrength: number): number {
return Math.max(0, -trendStrength);
}
/**
* 정배열/역배열 강도 분리 반환
* @param trendStrength 통합 추세 강도 (-100 ~ +100)
* @returns { bullish: 정배열 강도, bearish: 역배열 강도 }
*/
export function getStrengthValues(trendStrength: number): { bullish: number; bearish: number } {
return {
bullish: getBullishStrength(trendStrength),
bearish: getBearishStrength(trendStrength),
};
}
/**
* 추세 강도에 따른 색상 반환
* @param strength 추세 강도 값
* @returns 색상 코드
*/
export function getTrendColor(strength: number): string {
if (strength >= 50) return '#c62828'; // 강한 상승 - 빨간색
if (strength >= 20) return '#ef5350'; // 상승 - 연한 빨간색
if (strength >= -20) return '#9e9e9e'; // 중립 - 회색
if (strength >= -50) return '#42a5f5'; // 하락 - 연한 파란색
return '#1565c0'; // 강한 하락 - 파란색
}
/**
* 추세 강도 기준 정렬 함수
* @param items 정렬할 항목 배열
* @param sortMode 정렬 모드 ('default' | 'bullish' | 'bearish')
* @returns 정렬된 배열
*/
export function sortByTrendStrength<T extends { trendStrength?: number; koreanName: string }>(
items: T[],
sortMode: 'default' | 'bullish' | 'bearish'
): T[] {
const sorted = [...items];
switch (sortMode) {
case 'bullish':
// 정배열: 추세 강도가 높은 순 (내림차순)
return sorted.sort((a, b) => (b.trendStrength ?? 0) - (a.trendStrength ?? 0));
case 'bearish':
// 역배열: 추세 강도가 낮은 순 (오름차순)
return sorted.sort((a, b) => (a.trendStrength ?? 0) - (b.trendStrength ?? 0));
case 'default':
default:
// 기본: 이름순 정렬
return sorted.sort((a, b) => a.koreanName.localeCompare(b.koreanName, 'ko'));
}
}
/**
* 배치 추세 강도 계산 (여러 종목 동시 처리)
* @param symbolsWithData 종목별 OHLC 데이터 맵
* @returns 추세 강도 결과 배열
*/
export function calculateBatchTrendStrength(
symbolsWithData: Map<string, {
koreanName: string;
englishName?: string;
ohlcData: OHLCData[];
isFavorite?: boolean;
}>
): TrendStrengthResult[] {
const results: TrendStrengthResult[] = [];
symbolsWithData.forEach((data, symbol) => {
const strength = calculateTrendStrength(data.ohlcData);
if (strength) {
results.push({
symbol,
koreanName: data.koreanName,
englishName: data.englishName,
trendStrength: strength.trendStrength,
ma10: strength.ma10,
ma20: strength.ma20,
ma60: strength.ma60,
atr14: strength.atr14,
isFavorite: data.isFavorite,
});
} else {
// 데이터 부족 시 기본값
results.push({
symbol,
koreanName: data.koreanName,
englishName: data.englishName,
trendStrength: 0,
ma10: 0,
ma20: 0,
ma60: 0,
atr14: 0,
isFavorite: data.isFavorite,
});
}
});
return results;
}
// 사용 예시
/*
import { calculateTrendStrength, sortByTrendStrength, getTrendLabel, getTrendColor } from './trendStrength';
// 단일 종목 추세 강도 계산
const ohlcData = [
{ open: 100, high: 105, low: 98, close: 103 },
{ open: 103, high: 108, low: 101, close: 106 },
// ... 최소 60개 데이터
];
const result = calculateTrendStrength(ohlcData);
if (result) {
console.log(`추세 강도: ${result.trendStrength}`);
console.log(`라벨: ${getTrendLabel(result.trendStrength)}`);
console.log(`색상: ${getTrendColor(result.trendStrength)}`);
}
// 여러 종목 정렬
const items = [
{ symbol: 'BTC', koreanName: '비트코인', trendStrength: 75 },
{ symbol: 'ETH', koreanName: '이더리움', trendStrength: -30 },
{ symbol: 'XRP', koreanName: '리플', trendStrength: 45 },
];
// 정배열 순 정렬 (상승 추세가 강한 순)
const bullishSorted = sortByTrendStrength(items, 'bullish');
// 결과: BTC(75) -> XRP(45) -> ETH(-30)
// 역배열 순 정렬 (하락 추세가 강한 순)
const bearishSorted = sortByTrendStrength(items, 'bearish');
// 결과: ETH(-30) -> XRP(45) -> BTC(75)
*/