본문으로 건너뛰기
AIDevOps
  • Learn
  • Learning Paths
  • Practice
  • Open Source
  • Books
  • Engineering

    AI DevOpsAI 서비스 개발·운영 전체 지도LLMOpsLLM 배포·평가·관측실전 프로젝트AI Agent 프로젝트 실습

    Knowledge

    Docs기술 문서 모음Blog엔지니어링 아티클Plogger개발 기록 피드

    Validate

    Certification3단계 역량 인증 · 준비 중
AI Models
LlamaMistralGemmaDeepSeekQwen
🧠 AI Agent 개발
금융 AI AgentLLM API 서버주식 투자 AgentAIOps AI Agent교육 AI Agent코딩 AI Agent
🤖 AI 실전 개발
AI 실전 입문 & 로드맵Hugging FaceLangChainLlamaIndexLLMOps|LangGraphMCPMulti-AgentAgent Evaluation
🧠 AI Core
AI 입문 & 로드맵ML FundamentalsLLM Fundamentals|Python AIC++|PyTorchTensorFlowJAX
🌱 Spring Cloud
Spring 입문 & 로드맵Spring Cloud GatewaySpring BootJava|Spring AISpring SecuritySpring BatchSpring JPA
🐳 DevOps
DevOps 입문 & 로드맵LinuxDockerCI/CD|Kubernetes 기본K8s 심화/실무PrometheusGrafana
🧱 인프라
인프라 입문 & 로드맵NginxRedis
☁️ 클라우드
클라우드 입문 & 로드맵AWSGCPAzureNCPCloudflare
🎨 Frontend
Frontend 입문 & 로드맵JavaScriptTypeScript|ReactNext.js|VueNuxt
📱 Mobile
Mobile 입문 & 로드맵KotlinAndroidFlutter
⚙️ Backend
Backend 입문 & 로드맵Python 기본FastAPIDjangoFlask|CGoGinNode.js
💾 Database
DB 입문 & 로드맵공통 SQLOracleMySQLPostgreSQL|MongoDB벡터 DB
🧪 검증
k6JMeternGrinder
AIDevOps

Engineering AI. From Code to Production.
AI와 AI Agent를 개발하고 운영하기 위한 엔지니어링 학습 플랫폼

Learn

  • 전체 가이드
  • Learning Paths
  • Practice
  • Books

Resources

  • AI DevOps
  • LLMOps
  • 실전 프로젝트
  • Docs
  • Blog
  • Plogger
  • Open Source
  • Certification (준비 중)

Start Here

  • AI Core 로드맵
  • AI 실전 개발 로드맵
  • Spring Cloud 로드맵
  • DevOps 로드맵
  • 인프라 로드맵

 

  • 클라우드 로드맵
  • Frontend 로드맵
  • Mobile 로드맵
  • Backend 로드맵
  • Database 로드맵
© 2026 AI DevOps Korea. All rights reserved.
이용약관개인정보처리방침Sitemaptestforge.kr
금융 AI Agent 04

📊 기술적 분석 에이전트 Tool Use 패턴

Visitors

RSI, MACD, 볼린저밴드, 이동평균을 계산하고 매수·매도 시그널을 생성합니다. LLM이 지표를 직접 계산하는 Tool을 호출하고 결과를 해석해 투자자가 이해하기 쉬운 분석 리포트를 만듭니다.

← AI Agent 시리즈
📊

목차

0 / 9
  1. 기술적 지표 Tool 구현
  2. 이동평균 & 골든크로스
  3. RSI
  4. MACD
  5. 볼린저밴드
  6. 매수·매도 시그널 통합
  7. 간단한 백테스팅
  8. Tool Use Agent 구성
  9. 실행 예제
목차 9개 섹션
  1. 기술적 지표 Tool 구현
  2. 이동평균 & 골든크로스
  3. RSI
  4. MACD
  5. 볼린저밴드
  6. 매수·매도 시그널 통합
  7. 간단한 백테스팅
  8. Tool Use Agent 구성
  9. 실행 예제

기술적 지표 Tool 구현

pandas-ta 라이브러리로 주요 지표를 계산하고 각각을 LangChain Tool로 래핑합니다.

이동평균 & 골든크로스

tools/technical.pyPYTHON
import yfinance as yf
import pandas as pd
import pandas_ta as ta
from langchain_core.tools import tool
from pykrx import stock
from datetime import datetime, timedelta

def _get_ohlcv_us(ticker: str, period: str = "6mo") -> pd.DataFrame:
    df = yf.Ticker(ticker.upper()).history(period=period)
    df.index = pd.to_datetime(df.index).tz_localize(None)
    return df[["Open", "High", "Low", "Close", "Volume"]].dropna()

def _get_ohlcv_kr(ticker: str, days: int = 180) -> pd.DataFrame:
    end = datetime.today().strftime("%Y%m%d")
    start = (datetime.today() - timedelta(days=days)).strftime("%Y%m%d")
    df = stock.get_market_ohlcv_by_date(start, end, ticker)
    df.columns = ["Open", "High", "Low", "Close", "Volume"]
    return df.dropna()

@tool
def get_moving_average(ticker: str, market: str = "US", windows: str = "5,20,60") -> str:
    """
    이동평균선과 골든크로스/데드크로스 여부를 계산합니다.
    ticker: 종목코드 (미국: AAPL, 한국: 005930)
    market: US | KR
    windows: 이동평균 기간 (쉼표 구분, 예: "5,20,60,120")
    """
    df = _get_ohlcv_us(ticker) if market == "US" else _get_ohlcv_kr(ticker)
    periods = [int(w.strip()) for w in windows.split(",")]

    lines = [f"[{ticker} 이동평균 분석]"]
    curr_price = df["Close"].iloc[-1]
    lines.append(f"현재가: {curr_price:,.2f}")

    ma_values = {}
    for p in periods:
        if len(df) >= p:
            ma = df["Close"].rolling(p).mean().iloc[-1]
            ma_values[p] = ma
            position = "위" if curr_price > ma else "아래"
            lines.append(f"MA{p}: {ma:,.2f} (현재가 {position})")

    # 골든크로스 판단 (단기 MA가 장기 MA 위로)
    sorted_periods = sorted(ma_values.keys())
    if len(sorted_periods) >= 2:
        short, long_ = sorted_periods[0], sorted_periods[-1]
        short_ma_prev = df["Close"].rolling(short).mean().iloc[-2]
        long_ma_prev  = df["Close"].rolling(long_).mean().iloc[-2]
        short_ma_now  = ma_values[short]
        long_ma_now   = ma_values[long_]

        if short_ma_now > long_ma_now and short_ma_prev <= long_ma_prev:
            lines.append(f"🟢 골든크로스 발생 (MA{short} > MA{long_})")
        elif short_ma_now < long_ma_now and short_ma_prev >= long_ma_prev:
            lines.append(f"🔴 데드크로스 발생 (MA{short} < MA{long_})")
        else:
            trend = "MA 정배열" if short_ma_now > long_ma_now else "MA 역배열"
            lines.append(f"현재 추세: {trend}")

    return "\n".join(lines)

RSI

PYTHON
@tool
def get_rsi(ticker: str, market: str = "US", period: int = 14) -> str:
    """
    RSI(상대강도지수)를 계산해 과매수/과매도 여부를 판단합니다.
    ticker: 종목코드
    market: US | KR
    period: RSI 계산 기간 (기본 14일)
    """
    df = _get_ohlcv_us(ticker) if market == "US" else _get_ohlcv_kr(ticker)
    rsi_series = ta.rsi(df["Close"], length=period)

    if rsi_series is None or rsi_series.dropna().empty:
        return "RSI를 계산할 수 없습니다 (데이터 부족)."

    rsi_now  = rsi_series.iloc[-1]
    rsi_prev = rsi_series.iloc[-2]

    if rsi_now >= 70:
        signal = "🔴 과매수 구간 (매도 고려)"
    elif rsi_now <= 30:
        signal = "🟢 과매도 구간 (매수 고려)"
    elif rsi_now > rsi_prev:
        signal = "↗ RSI 상승 중 (모멘텀 강화)"
    else:
        signal = "↘ RSI 하락 중 (모멘텀 약화)"

    return (
        f"[{ticker} RSI({period})]\n"
        f"현재 RSI: {rsi_now:.1f}\n"
        f"전일 RSI: {rsi_prev:.1f}\n"
        f"판단: {signal}"
    )

MACD

PYTHON
@tool
def get_macd(ticker: str, market: str = "US") -> str:
    """
    MACD(이동평균 수렴확산지수)를 계산해 추세 전환 시그널을 제공합니다.
    ticker: 종목코드
    market: US | KR
    """
    df = _get_ohlcv_us(ticker) if market == "US" else _get_ohlcv_kr(ticker)
    macd_df = ta.macd(df["Close"], fast=12, slow=26, signal=9)

    if macd_df is None or macd_df.dropna().empty:
        return "MACD를 계산할 수 없습니다."

    macd     = macd_df["MACD_12_26_9"].iloc[-1]
    signal   = macd_df["MACDs_12_26_9"].iloc[-1]
    hist     = macd_df["MACDh_12_26_9"].iloc[-1]
    hist_prev = macd_df["MACDh_12_26_9"].iloc[-2]

    if hist > 0 and hist_prev <= 0:
        cross = "🟢 MACD 골든크로스 (상승 전환 시그널)"
    elif hist < 0 and hist_prev >= 0:
        cross = "🔴 MACD 데드크로스 (하락 전환 시그널)"
    elif hist > 0:
        cross = "📈 MACD 양봉 유지 (상승 추세)"
    else:
        cross = "📉 MACD 음봉 유지 (하락 추세)"

    return (
        f"[{ticker} MACD(12,26,9)]\n"
        f"MACD: {macd:.4f}\n"
        f"Signal: {signal:.4f}\n"
        f"Histogram: {hist:.4f}\n"
        f"판단: {cross}"
    )

볼린저밴드

PYTHON
@tool
def get_bollinger_bands(ticker: str, market: str = "US", period: int = 20) -> str:
    """
    볼린저밴드를 계산해 변동성과 가격 위치를 분석합니다.
    ticker: 종목코드
    market: US | KR
    period: 이동평균 기간 (기본 20일)
    """
    df = _get_ohlcv_us(ticker) if market == "US" else _get_ohlcv_kr(ticker)
    bb = ta.bbands(df["Close"], length=period, std=2)

    if bb is None or bb.dropna().empty:
        return "볼린저밴드를 계산할 수 없습니다."

    upper  = bb[f"BBU_{period}_2.0"].iloc[-1]
    middle = bb[f"BBM_{period}_2.0"].iloc[-1]
    lower  = bb[f"BBL_{period}_2.0"].iloc[-1]
    curr   = df["Close"].iloc[-1]
    width  = (upper - lower) / middle * 100  # 밴드폭 %

    pct_b  = (curr - lower) / (upper - lower) * 100  # 밴드 내 위치

    if curr >= upper:
        position = "🔴 상단 밴드 터치/돌파 (과매수 경고)"
    elif curr <= lower:
        position = "🟢 하단 밴드 터치/돌파 (과매도, 반등 기대)"
    elif pct_b >= 70:
        position = "↗ 상단 밴드 근접 (강세)"
    elif pct_b <= 30:
        position = "↘ 하단 밴드 근접 (약세)"
    else:
        position = "↔ 밴드 중앙부 (방향 탐색 중)"

    return (
        f"[{ticker} 볼린저밴드({period})]\n"
        f"상단: {upper:,.2f}\n"
        f"중심(MA{period}): {middle:,.2f}\n"
        f"하단: {lower:,.2f}\n"
        f"현재가: {curr:,.2f} (밴드 내 위치: {pct_b:.1f}%)\n"
        f"밴드폭: {width:.1f}%\n"
        f"판단: {position}"
    )

매수·매도 시그널 통합

tools/signal.pyPYTHON
from langchain_core.tools import tool
from tools.technical import (
    _get_ohlcv_us, _get_ohlcv_kr,
    get_rsi, get_macd, get_bollinger_bands, get_moving_average,
)
import pandas_ta as ta

@tool
def get_composite_signal(ticker: str, market: str = "US") -> str:
    """
    RSI, MACD, 볼린저밴드, 이동평균을 종합한 매수·매도 시그널을 생성합니다.
    ticker: 종목코드
    market: US | KR
    """
    df = _get_ohlcv_us(ticker) if market == "US" else _get_ohlcv_kr(ticker)

    scores = []

    # RSI
    rsi = ta.rsi(df["Close"], length=14).iloc[-1]
    if rsi <= 30:   scores.append(("RSI", +2, f"과매도({rsi:.1f})"))
    elif rsi <= 45: scores.append(("RSI", +1, f"중립 하단({rsi:.1f})"))
    elif rsi >= 70: scores.append(("RSI", -2, f"과매수({rsi:.1f})"))
    elif rsi >= 55: scores.append(("RSI", -1, f"중립 상단({rsi:.1f})"))
    else:           scores.append(("RSI",  0, f"중립({rsi:.1f})"))

    # MACD
    macd_df = ta.macd(df["Close"])
    if macd_df is not None and not macd_df.dropna().empty:
        hist = macd_df.iloc[-1, 2]
        prev = macd_df.iloc[-2, 2]
        if hist > 0 and prev <= 0:   scores.append(("MACD", +2, "골든크로스"))
        elif hist > 0:               scores.append(("MACD", +1, "양봉"))
        elif hist < 0 and prev >= 0: scores.append(("MACD", -2, "데드크로스"))
        else:                        scores.append(("MACD", -1, "음봉"))

    # 볼린저밴드
    bb = ta.bbands(df["Close"])
    if bb is not None and not bb.dropna().empty:
        curr = df["Close"].iloc[-1]
        upper = bb.iloc[-1, 0]
        lower = bb.iloc[-1, 2]
        pct_b = (curr - lower) / (upper - lower) * 100
        if pct_b <= 20:   scores.append(("BB", +2, f"하단근접({pct_b:.0f}%)"))
        elif pct_b >= 80: scores.append(("BB", -2, f"상단근접({pct_b:.0f}%)"))
        else:             scores.append(("BB",  0, f"중앙({pct_b:.0f}%)"))

    # 이동평균 정배열
    ma20  = df["Close"].rolling(20).mean().iloc[-1]
    ma60  = df["Close"].rolling(60).mean().iloc[-1]
    curr_p = df["Close"].iloc[-1]
    if curr_p > ma20 > ma60: scores.append(("MA", +1, "정배열"))
    elif curr_p < ma20 < ma60: scores.append(("MA", -1, "역배열"))
    else:                      scores.append(("MA",  0, "혼조"))

    total = sum(s for _, s, _ in scores)
    detail = " | ".join(f"{name}:{label}" for name, _, label in scores)

    if   total >= 4:  judgment = "🟢 강한 매수"
    elif total >= 2:  judgment = "🟡 약한 매수"
    elif total <= -4: judgment = "🔴 강한 매도"
    elif total <= -2: judgment = "🟠 약한 매도"
    else:             judgment = "⚪ 중립"

    return (
        f"[{ticker} 종합 시그널]\n"
        f"점수: {total:+d}/8\n"
        f"판단: {judgment}\n"
        f"근거: {detail}"
    )

간단한 백테스팅

tools/backtest.pyPYTHON
from langchain_core.tools import tool
import yfinance as yf
import pandas as pd
import pandas_ta as ta

@tool
def run_rsi_backtest(ticker: str, period: str = "1y",
                     rsi_buy: float = 30, rsi_sell: float = 70) -> str:
    """
    RSI 기반 단순 매수·매도 전략을 백테스팅합니다.
    ticker: 미국 주식 심볼 (예: AAPL)
    period: 백테스팅 기간 (1y | 2y | 5y)
    rsi_buy: 매수 기준 RSI (기본 30, 과매도)
    rsi_sell: 매도 기준 RSI (기본 70, 과매수)
    """
    df = yf.Ticker(ticker.upper()).history(period=period)
    df["RSI"] = ta.rsi(df["Close"], length=14)
    df = df.dropna()

    position = False
    buy_price = 0.0
    trades = []

    for i, row in df.iterrows():
        if not position and row["RSI"] <= rsi_buy:
            position = True
            buy_price = row["Close"]
        elif position and row["RSI"] >= rsi_sell:
            ret = (row["Close"] - buy_price) / buy_price * 100
            trades.append(ret)
            position = False

    if not trades:
        return f"{ticker} {period} 백테스트: 거래 발생 없음 (RSI {rsi_buy}/{rsi_sell} 조건 미충족)"

    total_ret = sum(trades)
    win_rate  = sum(1 for t in trades if t > 0) / len(trades) * 100
    bh_ret    = (df["Close"].iloc[-1] - df["Close"].iloc[0]) / df["Close"].iloc[0] * 100

    return (
        f"[{ticker} RSI({rsi_buy}/{rsi_sell}) 백테스트 — {period}]\n"
        f"총 거래 횟수: {len(trades)}회\n"
        f"승률: {win_rate:.1f}%\n"
        f"전략 누적 수익률: {total_ret:+.1f}%\n"
        f"Buy & Hold 수익률: {bh_ret:+.1f}%\n"
        f"초과 수익: {total_ret - bh_ret:+.1f}%"
    )
⚠️

백테스팅 한계: 이 백테스팅은 슬리피지, 수수료, 시장 충격을 고려하지 않은 단순 시뮬레이션입니다. 실제 거래 전략 수립에는 더 엄밀한 검증이 필요합니다.

Tool Use Agent 구성

agents/technical_agent.pyPYTHON
from langchain_ollama import ChatOllama
from langchain.agents import create_react_agent, AgentExecutor
from langchain_core.prompts import PromptTemplate

from tools.technical import get_moving_average, get_rsi, get_macd, get_bollinger_bands
from tools.signal import get_composite_signal
from tools.backtest import run_rsi_backtest
from tools.us_stock import get_us_stock_price
from tools.kr_stock import get_kr_stock_price

TECHNICAL_TOOLS = [
    get_composite_signal,
    get_moving_average,
    get_rsi,
    get_macd,
    get_bollinger_bands,
    run_rsi_backtest,
    get_us_stock_price,
    get_kr_stock_price,
]

SYSTEM = """당신은 주식 기술적 분석 전문가입니다.
RSI, MACD, 볼린저밴드, 이동평균 등 기술적 지표를 해석해 투자자에게 명확한 분석을 제공합니다.

분석 원칙:
- 먼저 종합 시그널을 확인하고, 세부 지표로 보완합니다
- 지표 수치를 구체적으로 제시합니다
- 기술적 분석의 한계를 명시합니다
- 투자 권유가 아닌 분석 정보만 제공합니다"""

def build_technical_agent() -> AgentExecutor:
    llm = ChatOllama(model="llama3.1:8b", temperature=0)
    prompt = PromptTemplate.from_template(
        SYSTEM + "\n\n사용 가능한 도구:\n{tools}\n\n도구 이름: {tool_names}\n\n질문: {input}\n{agent_scratchpad}"
    )
    agent = create_react_agent(llm, TECHNICAL_TOOLS, prompt)
    return AgentExecutor(agent=agent, tools=TECHNICAL_TOOLS, verbose=True,
                         max_iterations=6, handle_parsing_errors=True)

실행 예제

PYTHON
from agents.technical_agent import build_technical_agent

agent = build_technical_agent()

questions = [
    "AAPL 현재 RSI와 MACD를 분석해줘",
    "삼성전자(005930) 기술적 분석 종합 시그널은?",
    "NVDA 볼린저밴드 위치와 이동평균 정배열 여부를 알려줘",
    "AAPL에서 RSI 30/70 전략으로 1년 백테스팅 해줘",
    "SK하이닉스(000660) 골든크로스 발생했어?",
]

for q in questions:
    print(f"\nQ: {q}")
    result = agent.invoke({"input": q})
    print(f"A: {result['output']}")