goldenChat base source add
This commit is contained in:
@@ -0,0 +1,535 @@
|
||||
/**
|
||||
* 정배열/역배열 + Stochastic Slow 기반 매매 전략
|
||||
*
|
||||
* [정배열/역배열 조건]
|
||||
* - 정배열: 추세 강도 > 0
|
||||
* - 역배열: 추세 강도 < 0
|
||||
*
|
||||
* [매매 조건]
|
||||
* 1) 정배열인 경우:
|
||||
* - Stochastic Slow 20 상향 돌파 → 매수
|
||||
* - 50 상향 돌파 → 매수
|
||||
* - 80 상향 돌파 → 매수
|
||||
* - 20 하향 돌파 → 매도
|
||||
* - 50 하향 돌파 → 매도
|
||||
* - 80 하향 돌파 → 매도
|
||||
*
|
||||
* 2) 역배열인 경우:
|
||||
* - 80 상향 돌파 → 매수
|
||||
* - 80 하향 돌파 → 매도
|
||||
*/
|
||||
|
||||
// OHLCV + 지표 데이터 인터페이스
|
||||
export interface CandleWithIndicators {
|
||||
date: string;
|
||||
timestamp?: string;
|
||||
open: number;
|
||||
high: number;
|
||||
low: number;
|
||||
close: number;
|
||||
volume: number;
|
||||
stochasticK: number | null; // Stochastic Slow %K
|
||||
stochasticD: number | null; // Stochastic Slow %D
|
||||
macdLine?: number | null;
|
||||
signalLine?: number | null;
|
||||
macdHistogram?: number | null;
|
||||
trendStrength?: number; // 추세 강도 (-100 ~ +100)
|
||||
}
|
||||
|
||||
// 매매 신호 타입
|
||||
export type SignalType = 'BUY' | 'SELL' | 'HOLD';
|
||||
|
||||
// 매매 신호 결과
|
||||
export interface TradingSignal {
|
||||
date: string;
|
||||
signal: SignalType;
|
||||
reason: string;
|
||||
stochasticK: number;
|
||||
stochasticD: number;
|
||||
trendStrength: number;
|
||||
isBullish: boolean; // 정배열 여부
|
||||
crossLevel?: number; // 돌파 레벨 (20, 50, 80)
|
||||
crossDirection?: 'UP' | 'DOWN'; // 돌파 방향
|
||||
}
|
||||
|
||||
// 돌파 레벨 정의
|
||||
const OVERSOLD_LEVEL = 20;
|
||||
const NEUTRAL_LEVEL = 50;
|
||||
const OVERBOUGHT_LEVEL = 80;
|
||||
|
||||
/**
|
||||
* 상향 돌파 여부 확인
|
||||
* @param prevValue 이전 값
|
||||
* @param currValue 현재 값
|
||||
* @param level 돌파 레벨
|
||||
* @returns 상향 돌파 여부
|
||||
*/
|
||||
export function isCrossUp(prevValue: number, currValue: number, level: number): boolean {
|
||||
return prevValue < level && currValue >= level;
|
||||
}
|
||||
|
||||
/**
|
||||
* 하향 돌파 여부 확인
|
||||
* @param prevValue 이전 값
|
||||
* @param currValue 현재 값
|
||||
* @param level 돌파 레벨
|
||||
* @returns 하향 돌파 여부
|
||||
*/
|
||||
export function isCrossDown(prevValue: number, currValue: number, level: number): boolean {
|
||||
return prevValue > level && currValue <= level;
|
||||
}
|
||||
|
||||
/**
|
||||
* 정배열 여부 확인
|
||||
* @param trendStrength 추세 강도
|
||||
* @returns 정배열 여부 (추세 강도 > 0)
|
||||
*/
|
||||
export function isBullishTrend(trendStrength: number): boolean {
|
||||
return trendStrength > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 역배열 여부 확인
|
||||
* @param trendStrength 추세 강도
|
||||
* @returns 역배열 여부 (추세 강도 < 0)
|
||||
*/
|
||||
export function isBearishTrend(trendStrength: number): boolean {
|
||||
return trendStrength < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 단일 캔들에 대한 매매 신호 생성
|
||||
* @param current 현재 캔들 데이터
|
||||
* @param previous 이전 캔들 데이터
|
||||
* @param trendStrength 추세 강도
|
||||
* @returns 매매 신호 또는 null
|
||||
*/
|
||||
export function generateSignalForCandle(
|
||||
current: CandleWithIndicators,
|
||||
previous: CandleWithIndicators,
|
||||
trendStrength: number
|
||||
): TradingSignal | null {
|
||||
// Stochastic 값이 없으면 신호 없음
|
||||
if (current.stochasticK === null || previous.stochasticK === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currK = current.stochasticK;
|
||||
const prevK = previous.stochasticK;
|
||||
const isBullish = isBullishTrend(trendStrength);
|
||||
|
||||
// 정배열인 경우
|
||||
if (isBullish) {
|
||||
// 20 상향 돌파 → 매수
|
||||
if (isCrossUp(prevK, currK, OVERSOLD_LEVEL)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'BUY',
|
||||
reason: '정배열 + Stochastic 20 상향 돌파 (과매도 탈출)',
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: true,
|
||||
crossLevel: OVERSOLD_LEVEL,
|
||||
crossDirection: 'UP',
|
||||
};
|
||||
}
|
||||
|
||||
// 50 상향 돌파 → 매수
|
||||
if (isCrossUp(prevK, currK, NEUTRAL_LEVEL)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'BUY',
|
||||
reason: '정배열 + Stochastic 50 상향 돌파 (중립선 상향)',
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: true,
|
||||
crossLevel: NEUTRAL_LEVEL,
|
||||
crossDirection: 'UP',
|
||||
};
|
||||
}
|
||||
|
||||
// 80 상향 돌파 → 매수
|
||||
if (isCrossUp(prevK, currK, OVERBOUGHT_LEVEL)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'BUY',
|
||||
reason: '정배열 + Stochastic 80 상향 돌파 (강한 상승 모멘텀)',
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: true,
|
||||
crossLevel: OVERBOUGHT_LEVEL,
|
||||
crossDirection: 'UP',
|
||||
};
|
||||
}
|
||||
|
||||
// 20 하향 돌파 → 매도
|
||||
if (isCrossDown(prevK, currK, OVERSOLD_LEVEL)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'SELL',
|
||||
reason: '정배열 + Stochastic 20 하향 돌파 (과매도 진입)',
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: true,
|
||||
crossLevel: OVERSOLD_LEVEL,
|
||||
crossDirection: 'DOWN',
|
||||
};
|
||||
}
|
||||
|
||||
// 50 하향 돌파 → 매도
|
||||
if (isCrossDown(prevK, currK, NEUTRAL_LEVEL)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'SELL',
|
||||
reason: '정배열 + Stochastic 50 하향 돌파 (중립선 하향)',
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: true,
|
||||
crossLevel: NEUTRAL_LEVEL,
|
||||
crossDirection: 'DOWN',
|
||||
};
|
||||
}
|
||||
|
||||
// 80 하향 돌파 → 매도
|
||||
if (isCrossDown(prevK, currK, OVERBOUGHT_LEVEL)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'SELL',
|
||||
reason: '정배열 + Stochastic 80 하향 돌파 (과매수 이탈)',
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: true,
|
||||
crossLevel: OVERBOUGHT_LEVEL,
|
||||
crossDirection: 'DOWN',
|
||||
};
|
||||
}
|
||||
}
|
||||
// 역배열인 경우
|
||||
else if (isBearishTrend(trendStrength)) {
|
||||
// 80 상향 돌파 → 매수
|
||||
if (isCrossUp(prevK, currK, OVERBOUGHT_LEVEL)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'BUY',
|
||||
reason: '역배열 + Stochastic 80 상향 돌파 (반등 시그널)',
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: false,
|
||||
crossLevel: OVERBOUGHT_LEVEL,
|
||||
crossDirection: 'UP',
|
||||
};
|
||||
}
|
||||
|
||||
// 80 하향 돌파 → 매도
|
||||
if (isCrossDown(prevK, currK, OVERBOUGHT_LEVEL)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'SELL',
|
||||
reason: '역배열 + Stochastic 80 하향 돌파 (하락 지속)',
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: false,
|
||||
crossLevel: OVERBOUGHT_LEVEL,
|
||||
crossDirection: 'DOWN',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 신호 없음
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 전체 캔들 데이터에 대한 매매 신호 생성
|
||||
* @param candles 캔들 데이터 배열 (시간순 정렬)
|
||||
* @param trendStrength 추세 강도 (현재 종목의 추세)
|
||||
* @returns 매매 신호 배열
|
||||
*/
|
||||
export function generateSignals(
|
||||
candles: CandleWithIndicators[],
|
||||
trendStrength: number
|
||||
): TradingSignal[] {
|
||||
const signals: TradingSignal[] = [];
|
||||
|
||||
if (candles.length < 2) {
|
||||
return signals;
|
||||
}
|
||||
|
||||
for (let i = 1; i < candles.length; i++) {
|
||||
const signal = generateSignalForCandle(candles[i], candles[i - 1], trendStrength);
|
||||
if (signal) {
|
||||
signals.push(signal);
|
||||
}
|
||||
}
|
||||
|
||||
return signals;
|
||||
}
|
||||
|
||||
/**
|
||||
* 가장 최근 매매 신호 조회
|
||||
* @param candles 캔들 데이터 배열 (시간순 정렬)
|
||||
* @param trendStrength 추세 강도
|
||||
* @param lookbackPeriod 조회할 기간 (캔들 수)
|
||||
* @returns 가장 최근 매매 신호 또는 null
|
||||
*/
|
||||
export function getLatestSignal(
|
||||
candles: CandleWithIndicators[],
|
||||
trendStrength: number,
|
||||
lookbackPeriod: number = 10
|
||||
): TradingSignal | null {
|
||||
if (candles.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const startIdx = Math.max(1, candles.length - lookbackPeriod);
|
||||
|
||||
for (let i = candles.length - 1; i >= startIdx; i--) {
|
||||
const signal = generateSignalForCandle(candles[i], candles[i - 1], trendStrength);
|
||||
if (signal) {
|
||||
return signal;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 신호 요약 정보 생성
|
||||
* @param signals 매매 신호 배열
|
||||
* @returns 요약 정보
|
||||
*/
|
||||
export function getSignalSummary(signals: TradingSignal[]): {
|
||||
totalSignals: number;
|
||||
buySignals: number;
|
||||
sellSignals: number;
|
||||
lastSignal: TradingSignal | null;
|
||||
bullishSignals: number;
|
||||
bearishSignals: number;
|
||||
} {
|
||||
const buySignals = signals.filter(s => s.signal === 'BUY');
|
||||
const sellSignals = signals.filter(s => s.signal === 'SELL');
|
||||
const bullishSignals = signals.filter(s => s.isBullish);
|
||||
const bearishSignals = signals.filter(s => !s.isBullish);
|
||||
|
||||
return {
|
||||
totalSignals: signals.length,
|
||||
buySignals: buySignals.length,
|
||||
sellSignals: sellSignals.length,
|
||||
lastSignal: signals.length > 0 ? signals[signals.length - 1] : null,
|
||||
bullishSignals: bullishSignals.length,
|
||||
bearishSignals: bearishSignals.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 전략 설정 인터페이스
|
||||
*/
|
||||
export interface TrendStochasticStrategyConfig {
|
||||
oversoldLevel: number; // 과매도 레벨 (기본: 20)
|
||||
neutralLevel: number; // 중립 레벨 (기본: 50)
|
||||
overboughtLevel: number; // 과매수 레벨 (기본: 80)
|
||||
enableBullishAllLevels: boolean; // 정배열 시 모든 레벨 활성화 (기본: true)
|
||||
enableBearishOnlyOverbought: boolean; // 역배열 시 80 레벨만 활성화 (기본: true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본 전략 설정
|
||||
*/
|
||||
export const DEFAULT_STRATEGY_CONFIG: TrendStochasticStrategyConfig = {
|
||||
oversoldLevel: 20,
|
||||
neutralLevel: 50,
|
||||
overboughtLevel: 80,
|
||||
enableBullishAllLevels: true,
|
||||
enableBearishOnlyOverbought: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* 설정 가능한 전략 신호 생성
|
||||
* @param current 현재 캔들 데이터
|
||||
* @param previous 이전 캔들 데이터
|
||||
* @param trendStrength 추세 강도
|
||||
* @param config 전략 설정
|
||||
* @returns 매매 신호 또는 null
|
||||
*/
|
||||
export function generateSignalWithConfig(
|
||||
current: CandleWithIndicators,
|
||||
previous: CandleWithIndicators,
|
||||
trendStrength: number,
|
||||
config: TrendStochasticStrategyConfig = DEFAULT_STRATEGY_CONFIG
|
||||
): TradingSignal | null {
|
||||
if (current.stochasticK === null || previous.stochasticK === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currK = current.stochasticK;
|
||||
const prevK = previous.stochasticK;
|
||||
const isBullish = isBullishTrend(trendStrength);
|
||||
|
||||
// 정배열인 경우
|
||||
if (isBullish && config.enableBullishAllLevels) {
|
||||
// 과매도 레벨 상향 돌파 → 매수
|
||||
if (isCrossUp(prevK, currK, config.oversoldLevel)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'BUY',
|
||||
reason: `정배열 + Stochastic ${config.oversoldLevel} 상향 돌파`,
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: true,
|
||||
crossLevel: config.oversoldLevel,
|
||||
crossDirection: 'UP',
|
||||
};
|
||||
}
|
||||
|
||||
// 중립 레벨 상향 돌파 → 매수
|
||||
if (isCrossUp(prevK, currK, config.neutralLevel)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'BUY',
|
||||
reason: `정배열 + Stochastic ${config.neutralLevel} 상향 돌파`,
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: true,
|
||||
crossLevel: config.neutralLevel,
|
||||
crossDirection: 'UP',
|
||||
};
|
||||
}
|
||||
|
||||
// 과매수 레벨 상향 돌파 → 매수
|
||||
if (isCrossUp(prevK, currK, config.overboughtLevel)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'BUY',
|
||||
reason: `정배열 + Stochastic ${config.overboughtLevel} 상향 돌파`,
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: true,
|
||||
crossLevel: config.overboughtLevel,
|
||||
crossDirection: 'UP',
|
||||
};
|
||||
}
|
||||
|
||||
// 과매도 레벨 하향 돌파 → 매도
|
||||
if (isCrossDown(prevK, currK, config.oversoldLevel)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'SELL',
|
||||
reason: `정배열 + Stochastic ${config.oversoldLevel} 하향 돌파`,
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: true,
|
||||
crossLevel: config.oversoldLevel,
|
||||
crossDirection: 'DOWN',
|
||||
};
|
||||
}
|
||||
|
||||
// 중립 레벨 하향 돌파 → 매도
|
||||
if (isCrossDown(prevK, currK, config.neutralLevel)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'SELL',
|
||||
reason: `정배열 + Stochastic ${config.neutralLevel} 하향 돌파`,
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: true,
|
||||
crossLevel: config.neutralLevel,
|
||||
crossDirection: 'DOWN',
|
||||
};
|
||||
}
|
||||
|
||||
// 과매수 레벨 하향 돌파 → 매도
|
||||
if (isCrossDown(prevK, currK, config.overboughtLevel)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'SELL',
|
||||
reason: `정배열 + Stochastic ${config.overboughtLevel} 하향 돌파`,
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: true,
|
||||
crossLevel: config.overboughtLevel,
|
||||
crossDirection: 'DOWN',
|
||||
};
|
||||
}
|
||||
}
|
||||
// 역배열인 경우
|
||||
else if (isBearishTrend(trendStrength) && config.enableBearishOnlyOverbought) {
|
||||
// 과매수 레벨 상향 돌파 → 매수
|
||||
if (isCrossUp(prevK, currK, config.overboughtLevel)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'BUY',
|
||||
reason: `역배열 + Stochastic ${config.overboughtLevel} 상향 돌파`,
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: false,
|
||||
crossLevel: config.overboughtLevel,
|
||||
crossDirection: 'UP',
|
||||
};
|
||||
}
|
||||
|
||||
// 과매수 레벨 하향 돌파 → 매도
|
||||
if (isCrossDown(prevK, currK, config.overboughtLevel)) {
|
||||
return {
|
||||
date: current.date,
|
||||
signal: 'SELL',
|
||||
reason: `역배열 + Stochastic ${config.overboughtLevel} 하향 돌파`,
|
||||
stochasticK: currK,
|
||||
stochasticD: current.stochasticD ?? 0,
|
||||
trendStrength,
|
||||
isBullish: false,
|
||||
crossLevel: config.overboughtLevel,
|
||||
crossDirection: 'DOWN',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 사용 예시
|
||||
/*
|
||||
import {
|
||||
generateSignals,
|
||||
getLatestSignal,
|
||||
getSignalSummary,
|
||||
CandleWithIndicators
|
||||
} from './trendStochasticStrategy';
|
||||
|
||||
// 캔들 데이터 예시
|
||||
const candles: CandleWithIndicators[] = [
|
||||
{ date: '2024-01-01', open: 100, high: 105, low: 98, close: 103, volume: 1000, stochasticK: 18, stochasticD: 15 },
|
||||
{ date: '2024-01-02', open: 103, high: 110, low: 101, close: 108, volume: 1200, stochasticK: 22, stochasticD: 19 },
|
||||
// ...
|
||||
];
|
||||
|
||||
// 추세 강도 (양수: 정배열, 음수: 역배열)
|
||||
const trendStrength = 45.5; // 정배열
|
||||
|
||||
// 전체 신호 생성
|
||||
const signals = generateSignals(candles, trendStrength);
|
||||
console.log('전체 신호:', signals);
|
||||
|
||||
// 최근 신호 조회
|
||||
const latestSignal = getLatestSignal(candles, trendStrength);
|
||||
console.log('최근 신호:', latestSignal);
|
||||
|
||||
// 신호 요약
|
||||
const summary = getSignalSummary(signals);
|
||||
console.log('요약:', summary);
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user