feat: add trade history management with SQLite
- Implemented functions to record trades and PnL (profit and loss) logs. - Created database schema for trades, PnL logs, daily state, and budget anchors. - Added functionality to aggregate PnL by market and currency. - Included methods to check and mark daily actions to prevent duplicate processing. - Initialized the database on module load.
This commit is contained in:
parent
76b166fa8a
commit
548902c22a
|
|
@ -0,0 +1,17 @@
|
|||
.git
|
||||
.gitignore
|
||||
.venv
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.ruff_cache/
|
||||
.vscode/
|
||||
.env
|
||||
data/
|
||||
results/
|
||||
reports/
|
||||
eval_results/
|
||||
assets/
|
||||
README.md
|
||||
LICENSE
|
||||
64
.env.example
64
.env.example
|
|
@ -4,3 +4,67 @@ GOOGLE_API_KEY=
|
|||
ANTHROPIC_API_KEY=
|
||||
XAI_API_KEY=
|
||||
OPENROUTER_API_KEY=
|
||||
|
||||
# Discord Bot
|
||||
DISCORD_BOT_TOKEN=
|
||||
# 봇이 동작할 채널 ID (쉼표로 여러 개 지정, 비워두면 모든 채널에서 동작)
|
||||
# 채널 ID 확인: Discord 개발자 모드 켜고 → 채널 우클릭 → "채널 ID 복사"
|
||||
# DISCORD_CHANNEL_IDS=123456789012345678,987654321098765432
|
||||
|
||||
# 한국투자증권 API (모의투자/실전 모두 지원)
|
||||
# 발급: https://apiportal.koreainvestment.com → API신청 → 앱키 발급
|
||||
KIS_APP_KEY=
|
||||
KIS_APP_SECRET=
|
||||
KIS_ACCOUNT_NO=12345678-01
|
||||
KIS_VIRTUAL=true
|
||||
# 수동(/분석, /대형주, /매수) 1회 매수 예산 상한 (원)
|
||||
KIS_MAX_ORDER_AMOUNT=1000000
|
||||
# 한국 대형주/ETF 워치리스트 (비우면 응답 가능한 랭킹으로 대체)
|
||||
KR_WATCHLIST=005930,000660,005380,005490,035420,105560,069500,114800,226490,229200
|
||||
|
||||
# ─── 미국(US) 거래 설정 ───────────────────────────────────────
|
||||
# 미국 자동주문 기능 활성화 (기본: false)
|
||||
ENABLE_US_TRADING=false
|
||||
# 수동(/분석, /매수) 1회 매수 예산 상한 (USD)
|
||||
US_MAX_ORDER_AMOUNT=5000
|
||||
# 해외 종목 거래소 탐색 순서
|
||||
US_EXCHANGE_SEARCH_ORDER=NASD,NYSE,AMEX
|
||||
# 미국 대형주/ETF 워치리스트
|
||||
US_WATCHLIST=AAPL,MSFT,NVDA,AMZN,GOOGL,META,TSLA,AMD,AVGO,QQQ,SPY
|
||||
|
||||
# ─── 데이 트레이딩 설정 ───────────────────────────────────
|
||||
# 매일 매수할 종목 수 (기본: 5)
|
||||
DAY_TRADE_PICKS=5
|
||||
# 자동매수 기준 자금에서 사용할 비율 (0.5, 50% 둘 다 가능 / 기본: 100%)
|
||||
AUTO_BUY_BUDGET_RATIO=1.0
|
||||
# 자동 매수 시각 (KST, HH:MM) — 기본: 09:30 (스코어링→AI분석→매수)
|
||||
AUTO_BUY_TIME=09:30
|
||||
# 자동 매도 시각 (KST, HH:MM) — 기본: 15:20
|
||||
AUTO_SELL_TIME=15:20
|
||||
# 분석 보고서 저장 디렉터리 (도커 기본: /app/reports)
|
||||
REPORTS_DIR=reports
|
||||
# 자동매매 분석 보고서 디스코드 업로드 여부 (저장은 항상 수행)
|
||||
AUTO_REPORT_UPLOAD=true
|
||||
|
||||
# ─── 미국 데이 트레이딩 설정 (뉴욕시간 ET) ────────────────────
|
||||
# 매일 매수할 종목 수 (기본: 5)
|
||||
US_DAY_TRADE_PICKS=5
|
||||
# 미국 자동매수 기준 자금 비율 (미설정 시 AUTO_BUY_BUDGET_RATIO 사용)
|
||||
US_AUTO_BUY_BUDGET_RATIO=1.0
|
||||
# 자동 매수 시각 (ET, HH:MM) — 기본: 09:35
|
||||
US_AUTO_BUY_TIME=09:35
|
||||
# 자동 매도 시각 (ET, HH:MM) — 기본: 15:50
|
||||
US_AUTO_SELL_TIME=15:50
|
||||
|
||||
# ─── 손절/익절 설정 ────────────────────────────────────────
|
||||
# 손절 라인 (%, 음수) — 기본: -5%
|
||||
STOP_LOSS_PCT=-5.0
|
||||
# 익절 라인 (%, 양수) — 기본: 10%
|
||||
TAKE_PROFIT_PCT=10.0
|
||||
# 모니터링 간격 (분) — 기본: 30분
|
||||
MONITOR_INTERVAL_MIN=30
|
||||
|
||||
# (Optional) LLM model overrides for Discord bot
|
||||
# DEEP_THINK_LLM=gemini-3-flash-preview
|
||||
# QUICK_THINK_LLM=gemini-3-flash-preview
|
||||
# MAX_DEBATE_ROUNDS=1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
# TradingAgents 프로젝트 가이드라인
|
||||
|
||||
## 프로젝트 개요
|
||||
|
||||
멀티 에이전트 LLM 데이 트레이딩 프레임워크. LangGraph 기반 5단계 파이프라인(분석→토론→트레이딩→리스크→포트폴리오)으로 주식 매매 의사결정을 수행한다. Discord 봇 + 한국투자증권 API 연동으로 실제 자동매매를 지원한다.
|
||||
|
||||
## 빌드 및 실행
|
||||
|
||||
```bash
|
||||
pip install -e . # 개발 설치
|
||||
tradingagents analyze # CLI 대화형 분석
|
||||
python bot.py # Discord 봇 (자동 데이트레이딩)
|
||||
python main.py # 직접 분석 예제
|
||||
docker-compose up # 컨테이너 배포
|
||||
```
|
||||
|
||||
- Python 3.10+ 필수
|
||||
- `.env` 파일에 API 키 설정 필요 (LLM, Discord, KIS)
|
||||
|
||||
## 아키텍처
|
||||
|
||||
### 핵심 디렉터리
|
||||
|
||||
| 디렉터리 | 역할 |
|
||||
|----------|------|
|
||||
| `tradingagents/agents/` | 에이전트 팀 (analysts, researchers, trader, risk_mgmt, managers) |
|
||||
| `tradingagents/dataflows/` | 데이터 벤더 추상화 (yfinance/AlphaVantage) |
|
||||
| `tradingagents/graph/` | LangGraph 오케스트레이션, 상태 관리 |
|
||||
| `tradingagents/llm_clients/` | LLM 프로바이더 팩토리 (openai, anthropic, google 등) |
|
||||
| `cli/` | Typer 기반 대화형 CLI |
|
||||
|
||||
### 진입점
|
||||
|
||||
| 파일 | 용도 |
|
||||
|------|------|
|
||||
| `bot.py` | Discord 봇 (슬래시 커맨드 + 자동 스케줄) |
|
||||
| `main.py` | Python 직접 사용 예제 |
|
||||
| `kis_client.py` | 한국투자증권 REST API 클라이언트 |
|
||||
| `trade_history.py` | SQLite 거래 기록 관리 |
|
||||
|
||||
### 5단계 파이프라인
|
||||
|
||||
1. **애널리스트** (병렬): 시장/소셜/뉴스/펀더멘털 → 각 리포트
|
||||
2. **리서치 토론**: 강세 vs 약세 연구원 → 리서치 매니저 중재
|
||||
3. **트레이더**: 리포트 + BM25 메모리 기반 투자 계획 수립
|
||||
4. **리스크 토론**: 공격/중립/보수 → 리스크 심판
|
||||
5. **포트폴리오 매니저**: 최종 BUY/HOLD/SELL 결정
|
||||
|
||||
## 코드 컨벤션
|
||||
|
||||
### 에이전트 패턴
|
||||
|
||||
- 에이전트 생성: `create_*` 팩토리 함수 (예: `create_market_analyst(llm)`)
|
||||
- 그래프 노드: `node_func(state) → updated_state` 형태
|
||||
- 도구: LangChain 도구 바인딩, 벤더 추상화 레이어로 라우팅
|
||||
- 상태: `AgentState` TypedDict로 단계 간 데이터 전달
|
||||
|
||||
### 설정 관리
|
||||
|
||||
- `tradingagents/default_config.py`의 `DEFAULT_CONFIG` dict를 복사하여 사용
|
||||
- LLM 프로바이더, 토론 라운드, 데이터 벤더 등 중앙 관리
|
||||
- 환경변수: `.env` 파일 참조 (API 키, 스케줄, 한도 등)
|
||||
|
||||
### LLM 클라이언트
|
||||
|
||||
- `create_llm_client(provider, model, **kwargs)` 팩토리로 생성
|
||||
- 지원: openai, anthropic, google, xai, ollama, openrouter
|
||||
- Google: `thinking_level`, OpenAI: `reasoning_effort` 파라미터
|
||||
|
||||
### 데이터 벤더
|
||||
|
||||
- `dataflows/interface.py`에서 도구별 벤더 라우팅
|
||||
- 기본: yfinance (무료), 대안: AlphaVantage (유료)
|
||||
- 벤더 실패 시 자동 폴백
|
||||
|
||||
## 주의사항
|
||||
|
||||
- KIS API는 `KIS_VIRTUAL=true`로 모의투자 먼저 테스트
|
||||
- `bot.py`의 자동매매 스케줄(09:30/15:20 KST)은 실제 주문 실행 — 신중하게 수정
|
||||
- 커밋 메시지는 한국어로 간결하게 작성
|
||||
- Docker 배포 시 `data/`, `results/`, `reports/` 볼륨 마운트 필수
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
description: "코드 변경 작업 완료 후 항상 git add, commit, push를 수행하도록 지시합니다."
|
||||
---
|
||||
|
||||
# Git Push 자동 수행
|
||||
|
||||
코드 변경 작업을 완료한 후, 반드시 다음 단계를 순서대로 수행하세요:
|
||||
|
||||
1. **버전 업데이트** — `bot.py`의 `BOT_VERSION` 값을 변경 내용에 맞게 올린다
|
||||
2. **git add** — GitKraken MCP 도구(`mcp_gitkraken_git_add_or_commit` action: add)로 스테이징
|
||||
3. **git commit** — GitKraken MCP 도구(`mcp_gitkraken_git_add_or_commit` action: commit)로 커밋 (한국어 메시지)
|
||||
4. **git push** — GitKraken MCP 도구(`mcp_gitkraken_git_push`)로 푸시
|
||||
|
||||
## 중요: 도구 사용 규칙
|
||||
|
||||
- **터미널 명령(`git add`, `git commit`, `git push`) 대신 반드시 MCP 또는 전용 도구를 사용한다.**
|
||||
- GitKraken MCP 도구를 우선 사용하고, 사용 불가 시에만 터미널을 대안으로 사용한다.
|
||||
|
||||
## 버전 규칙 (semver)
|
||||
|
||||
- `bot.py` 상단의 `BOT_VERSION = "X.Y.Z"` 를 반드시 업데이트한다.
|
||||
- **patch (+0.0.1)**: 버그 수정, 작은 변경, 리팩토링
|
||||
- **minor (+0.1.0)**: 새 기능 추가, 기존 기능 개선
|
||||
- **major (+1.0.0)**: 사용자가 명시적으로 요청한 경우에만
|
||||
- 판단이 어려우면 patch를 올린다.
|
||||
|
||||
## 일반 규칙
|
||||
|
||||
- 커밋 메시지는 변경 내용을 명확하게 설명해야 합니다.
|
||||
- 이미 커밋/푸시할 변경 사항이 없으면 건너뜁니다.
|
||||
- 사용자가 명시적으로 "푸시하지 마" 또는 "커밋하지 마"라고 요청하면 이 규칙을 따르지 않습니다.
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
name: Docker CI/CD (GHCR)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
branches: [main]
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set image name
|
||||
run: echo "IMAGE_NAME=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Login to GHCR
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
|
@ -216,4 +216,4 @@ __marimo__/
|
|||
.streamlit/secrets.toml
|
||||
|
||||
# Cache
|
||||
**/data_cache/
|
||||
**/data_cache/
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
FROM python:3.12-slim
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PIP_NO_CACHE_DIR=1 \
|
||||
TZ=Asia/Seoul
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --upgrade pip && pip install -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["python", "-u", "bot.py"]
|
||||
534
README.md
534
README.md
|
|
@ -2,220 +2,456 @@
|
|||
<img src="assets/TauricResearch.png" style="width: 60%; height: auto;">
|
||||
</p>
|
||||
|
||||
<div align="center" style="line-height: 1;">
|
||||
<a href="https://arxiv.org/abs/2412.20138" target="_blank"><img alt="arXiv" src="https://img.shields.io/badge/arXiv-2412.20138-B31B1B?logo=arxiv"/></a>
|
||||
<a href="https://discord.com/invite/hk9PGKShPK" target="_blank"><img alt="Discord" src="https://img.shields.io/badge/Discord-TradingResearch-7289da?logo=discord&logoColor=white&color=7289da"/></a>
|
||||
<a href="./assets/wechat.png" target="_blank"><img alt="WeChat" src="https://img.shields.io/badge/WeChat-TauricResearch-brightgreen?logo=wechat&logoColor=white"/></a>
|
||||
<a href="https://x.com/TauricResearch" target="_blank"><img alt="X Follow" src="https://img.shields.io/badge/X-TauricResearch-white?logo=x&logoColor=white"/></a>
|
||||
<br>
|
||||
<a href="https://github.com/TauricResearch/" target="_blank"><img alt="Community" src="https://img.shields.io/badge/Join_GitHub_Community-TauricResearch-14C290?logo=discourse"/></a>
|
||||
</div>
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<!-- Keep these links. Translations will automatically update with the README. -->
|
||||
<a href="https://www.readme-i18n.com/TauricResearch/TradingAgents?lang=de">Deutsch</a> |
|
||||
<a href="https://www.readme-i18n.com/TauricResearch/TradingAgents?lang=es">Español</a> |
|
||||
<a href="https://www.readme-i18n.com/TauricResearch/TradingAgents?lang=fr">français</a> |
|
||||
<a href="https://www.readme-i18n.com/TauricResearch/TradingAgents?lang=ja">日本語</a> |
|
||||
<a href="https://www.readme-i18n.com/TauricResearch/TradingAgents?lang=ko">한국어</a> |
|
||||
<a href="https://www.readme-i18n.com/TauricResearch/TradingAgents?lang=pt">Português</a> |
|
||||
<a href="https://www.readme-i18n.com/TauricResearch/TradingAgents?lang=ru">Русский</a> |
|
||||
<a href="https://www.readme-i18n.com/TauricResearch/TradingAgents?lang=zh">中文</a>
|
||||
</div>
|
||||
# TradingAgents: Discord 기반 멀티 에이전트 자동매매 봇
|
||||
|
||||
> TradingAgents 프레임워크에 Discord 봇, 한국투자증권 Open API, KR/US 워치리스트 전략을 결합한 자동매매 프로젝트입니다.
|
||||
|
||||
---
|
||||
|
||||
# TradingAgents: Multi-Agents LLM Financial Trading Framework
|
||||
## 목차
|
||||
|
||||
## News
|
||||
- [2026-03] **TradingAgents v0.2.2** released with GPT-5.4/Gemini 3.1/Claude 4.6 model coverage, five-tier rating scale, OpenAI Responses API, Anthropic effort control, and cross-platform stability.
|
||||
- [2026-02] **TradingAgents v0.2.0** released with multi-provider LLM support (GPT-5.x, Gemini 3.x, Claude 4.x, Grok 4.x) and improved system architecture.
|
||||
- [2026-01] **Trading-R1** [Technical Report](https://arxiv.org/abs/2509.11420) released, with [Terminal](https://github.com/TauricResearch/Trading-R1) expected to land soon.
|
||||
- [한눈에 보기](#한눈에-보기)
|
||||
- [전략 개요](#전략-개요)
|
||||
- [에이전트 아키텍처](#에이전트-아키텍처)
|
||||
- [설치](#설치)
|
||||
- [환경 설정](#환경-설정)
|
||||
- [사용법](#사용법)
|
||||
- [KIS API 연동](#kis-api-연동)
|
||||
- [LLM과 데이터 소스](#llm과-데이터-소스)
|
||||
- [프로젝트 구조](#프로젝트-구조)
|
||||
- [운영 메모](#운영-메모)
|
||||
- [Citation](#citation)
|
||||
|
||||
<div align="center">
|
||||
<a href="https://www.star-history.com/#TauricResearch/TradingAgents&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=TauricResearch/TradingAgents&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=TauricResearch/TradingAgents&type=Date" />
|
||||
<img alt="TradingAgents Star History" src="https://api.star-history.com/svg?repos=TauricResearch/TradingAgents&type=Date" style="width: 80%; height: auto;" />
|
||||
</picture>
|
||||
</a>
|
||||
</div>
|
||||
---
|
||||
|
||||
> 🎉 **TradingAgents** officially released! We have received numerous inquiries about the work, and we would like to express our thanks for the enthusiasm in our community.
|
||||
>
|
||||
> So we decided to fully open-source the framework. Looking forward to building impactful projects with you!
|
||||
## 한눈에 보기
|
||||
|
||||
<div align="center">
|
||||
- Discord 슬래시 명령 11개로 분석, 잔고조회, 수동 주문, 스코어링, 손익 조회를 제공합니다.
|
||||
- 한국(KR)과 미국(US) 모두 워치리스트 기반 스코어링 뒤 상위 후보만 AI 분석합니다.
|
||||
- 자동매매는 `룰 기반 후보 선정 -> 상위 N개 AI 분석 -> BUY 종목만 균등매수 -> 오후 점검 -> 손절/익절 감시` 흐름으로 동작합니다.
|
||||
- 자동매수 예산은 `기준 자금(anchor) × 비율`로 계산할 수 있어, 예를 들어 `50%` 설정 시 절반씩 회전 투자할 수 있습니다.
|
||||
- 분석 보고서는 Markdown 파일로 저장되며, 필요하면 Discord에도 자동 업로드합니다.
|
||||
- 매매 이력과 실현손익은 SQLite로 누적 관리합니다.
|
||||
|
||||
🚀 [TradingAgents](#tradingagents-framework) | ⚡ [Installation & CLI](#installation-and-cli) | 🎬 [Demo](https://www.youtube.com/watch?v=90gr5lwjIho) | 📦 [Package Usage](#tradingagents-package) | 🤝 [Contributing](#contributing) | 📄 [Citation](#citation)
|
||||
---
|
||||
|
||||
</div>
|
||||
## 전략 개요
|
||||
|
||||
## TradingAgents Framework
|
||||
### 전체 흐름
|
||||
|
||||
TradingAgents is a multi-agent trading framework that mirrors the dynamics of real-world trading firms. By deploying specialized LLM-powered agents: from fundamental analysts, sentiment experts, and technical analysts, to trader, risk management team, the platform collaboratively evaluates market conditions and informs trading decisions. Moreover, these agents engage in dynamic discussions to pinpoint the optimal strategy.
|
||||
```text
|
||||
워치리스트/랭킹 후보 수집
|
||||
-> 룰 기반 점수 계산
|
||||
-> 상위 후보만 TradingAgentsGraph AI 분석
|
||||
-> BUY 종목만 균등분할 매수
|
||||
-> 오후 매도 점검
|
||||
-> 손절/익절 모니터링
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/schema.png" style="width: 100%; height: auto;">
|
||||
</p>
|
||||
### 시장별 자동매매 흐름
|
||||
|
||||
> TradingAgents framework is designed for research purposes. Trading performance may vary based on many factors, including the chosen backbone language models, model temperature, trading periods, the quality of data, and other non-deterministic factors. [It is not intended as financial, investment, or trading advice.](https://tauric.ai/disclaimer/)
|
||||
| 시장 | 후보 풀 | 자동 매수 | 오후 점검 | 장중 감시 |
|
||||
|------|---------|-----------|-----------|-----------|
|
||||
| KR | `KR_WATCHLIST` 우선, 비어 있으면 시총/거래량 랭킹 fallback | `AUTO_BUY_TIME` (기본 `09:30` KST), 예산 `AUTO_BUY_BUDGET_RATIO` 적용 | `AUTO_SELL_TIME` (기본 `15:20` KST) | `MONITOR_INTERVAL_MIN` 간격 |
|
||||
| US | `US_WATCHLIST` 우선, 비어 있으면 시총/거래량 랭킹 fallback | `US_AUTO_BUY_TIME` (기본 `09:35` ET), 예산 `US_AUTO_BUY_BUDGET_RATIO` 적용 | `US_AUTO_SELL_TIME` (기본 `15:50` ET) | `MONITOR_INTERVAL_MIN` 간격 |
|
||||
|
||||
Our framework decomposes complex trading tasks into specialized roles. This ensures the system achieves a robust, scalable approach to market analysis and decision-making.
|
||||
### 현재 스코어링 규칙
|
||||
|
||||
### Analyst Team
|
||||
- Fundamentals Analyst: Evaluates company financials and performance metrics, identifying intrinsic values and potential red flags.
|
||||
- Sentiment Analyst: Analyzes social media and public sentiment using sentiment scoring algorithms to gauge short-term market mood.
|
||||
- News Analyst: Monitors global news and macroeconomic indicators, interpreting the impact of events on market conditions.
|
||||
- Technical Analyst: Utilizes technical indicators (like MACD and RSI) to detect trading patterns and forecast price movements.
|
||||
KR/US 공통 골격은 같습니다.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/analyst.png" width="100%" style="display: inline-block; margin: 0 2%;">
|
||||
</p>
|
||||
- 워치리스트 기본 점수: `+30`
|
||||
- 등락률 `0~2%`: `+25`
|
||||
- 등락률 `2~5%`: `+15`
|
||||
- 시가총액 랭크 진입: `+10`
|
||||
- 거래량 랭크 진입: `+5`
|
||||
- 필터: 등락률 `> 8%` 또는 `< -5%`면 제외
|
||||
|
||||
### Researcher Team
|
||||
- Comprises both bullish and bearish researchers who critically assess the insights provided by the Analyst Team. Through structured debates, they balance potential gains against inherent risks.
|
||||
추가 메모:
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/researcher.png" width="70%" style="display: inline-block; margin: 0 2%;">
|
||||
</p>
|
||||
- KR은 KIS 현재가와 yfinance 전일 종가를 함께 써서 점수를 계산합니다.
|
||||
- US는 yfinance 가격 이력을 기본으로 쓰고, KIS 미국 현재가가 가능하면 현재가를 보정합니다.
|
||||
- 랭킹 보너스는 응답이 있을 때만 붙습니다.
|
||||
- 현재 코드 기준으로 미국 시총/거래량 랭킹은 모의투자에서 비활성 처리되어, 실전 환경에서만 반영됩니다.
|
||||
|
||||
### Trader Agent
|
||||
- Composes reports from the analysts and researchers to make informed trading decisions. It determines the timing and magnitude of trades based on comprehensive market insights.
|
||||
### 오후 매도 정책
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/trader.png" width="70%" style="display: inline-block; margin: 0 2%;">
|
||||
</p>
|
||||
- 워치리스트에 포함된 종목은 강제 청산하지 않고 스윙 보유합니다.
|
||||
- 워치리스트 밖 보유 종목만 오후 점검 시 시장가 매도합니다.
|
||||
- 손절/익절 조건은 워치리스트 여부와 관계없이 계속 감시합니다.
|
||||
|
||||
### Risk Management and Portfolio Manager
|
||||
- Continuously evaluates portfolio risk by assessing market volatility, liquidity, and other risk factors. The risk management team evaluates and adjusts trading strategies, providing assessment reports to the Portfolio Manager for final decision.
|
||||
- The Portfolio Manager approves/rejects the transaction proposal. If approved, the order will be sent to the simulated exchange and executed.
|
||||
### 손절/익절 규칙
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/risk.png" width="70%" style="display: inline-block; margin: 0 2%;">
|
||||
</p>
|
||||
- 손절: `STOP_LOSS_PCT` 이하
|
||||
- 익절: `TAKE_PROFIT_PCT` 이상
|
||||
- 감시 주기: `MONITOR_INTERVAL_MIN` 분
|
||||
|
||||
## Installation and CLI
|
||||
---
|
||||
|
||||
### Installation
|
||||
## 에이전트 아키텍처
|
||||
|
||||
TradingAgentsGraph는 아래 구조로 동작합니다.
|
||||
|
||||
### 1. 애널리스트 팀
|
||||
|
||||
- 시장 애널리스트: 기술적 지표와 차트 흐름 분석
|
||||
- 소셜 미디어 애널리스트: 투자 심리와 센티먼트 분석
|
||||
- 뉴스 애널리스트: 뉴스와 이벤트 리스크 분석
|
||||
- 펀더멘털 애널리스트: 재무와 사업 체력 분석
|
||||
|
||||
### 2. 리서치 팀
|
||||
|
||||
- 강세 리서처
|
||||
- 약세 리서처
|
||||
- 리서치 매니저
|
||||
|
||||
### 3. 트레이딩/리스크 팀
|
||||
|
||||
- 트레이더
|
||||
- 공격적 리스크 매니저
|
||||
- 보수적 리스크 매니저
|
||||
- 중립적 리스크 매니저
|
||||
|
||||
### 4. 최종 의사결정
|
||||
|
||||
- 포트폴리오 매니저가 `BUY / SELL / HOLD`를 확정합니다.
|
||||
|
||||
---
|
||||
|
||||
## 설치
|
||||
|
||||
### 1. 레포지토리 클론
|
||||
|
||||
Clone TradingAgents:
|
||||
```bash
|
||||
git clone https://github.com/TauricResearch/TradingAgents.git
|
||||
cd TradingAgents
|
||||
```
|
||||
|
||||
Create a virtual environment in any of your favorite environment managers:
|
||||
```bash
|
||||
conda create -n tradingagents python=3.13
|
||||
conda activate tradingagents
|
||||
```
|
||||
|
||||
Install the package and its dependencies:
|
||||
```bash
|
||||
pip install .
|
||||
```
|
||||
|
||||
### Required APIs
|
||||
|
||||
TradingAgents supports multiple LLM providers. Set the API key for your chosen provider:
|
||||
### 2. 가상환경
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=... # OpenAI (GPT)
|
||||
export GOOGLE_API_KEY=... # Google (Gemini)
|
||||
export ANTHROPIC_API_KEY=... # Anthropic (Claude)
|
||||
export XAI_API_KEY=... # xAI (Grok)
|
||||
export OPENROUTER_API_KEY=... # OpenRouter
|
||||
export ALPHA_VANTAGE_API_KEY=... # Alpha Vantage
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
For local models, configure Ollama with `llm_provider: "ollama"` in your config.
|
||||
Windows는 아래를 사용하세요.
|
||||
|
||||
```bash
|
||||
.venv\Scripts\activate
|
||||
```
|
||||
|
||||
### 3. 의존성 설치
|
||||
|
||||
Discord 봇을 바로 실행하려면 아래 조합이 가장 안전합니다.
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt python-dotenv
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
### 4. 환경변수 파일 준비
|
||||
|
||||
Alternatively, copy `.env.example` to `.env` and fill in your keys:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### CLI Usage
|
||||
---
|
||||
|
||||
## 환경 설정
|
||||
|
||||
기본 템플릿은 [`.env.example`](/home/devuser/projects/TradingAgents2/.env.example)에 있습니다.
|
||||
|
||||
```env
|
||||
# LLM Providers
|
||||
OPENAI_API_KEY=
|
||||
GOOGLE_API_KEY=
|
||||
ANTHROPIC_API_KEY=
|
||||
XAI_API_KEY=
|
||||
OPENROUTER_API_KEY=
|
||||
|
||||
# Discord
|
||||
DISCORD_BOT_TOKEN=
|
||||
# DISCORD_CHANNEL_IDS=123456789012345678,987654321098765432
|
||||
|
||||
# KIS
|
||||
KIS_APP_KEY=
|
||||
KIS_APP_SECRET=
|
||||
KIS_ACCOUNT_NO=12345678-01
|
||||
KIS_VIRTUAL=true
|
||||
KIS_MAX_ORDER_AMOUNT=1000000
|
||||
KR_WATCHLIST=005930,000660,005380,005490,035420,105560,069500,114800,226490,229200
|
||||
|
||||
# US
|
||||
ENABLE_US_TRADING=false
|
||||
US_MAX_ORDER_AMOUNT=5000
|
||||
US_EXCHANGE_SEARCH_ORDER=NASD,NYSE,AMEX
|
||||
US_WATCHLIST=AAPL,MSFT,NVDA,AMZN,GOOGL,META,TSLA,AMD,AVGO,QQQ,SPY
|
||||
|
||||
# Schedule
|
||||
DAY_TRADE_PICKS=5
|
||||
AUTO_BUY_BUDGET_RATIO=1.0
|
||||
AUTO_BUY_TIME=09:30
|
||||
AUTO_SELL_TIME=15:20
|
||||
US_DAY_TRADE_PICKS=5
|
||||
US_AUTO_BUY_BUDGET_RATIO=1.0
|
||||
US_AUTO_BUY_TIME=09:35
|
||||
US_AUTO_SELL_TIME=15:50
|
||||
|
||||
# Risk / reports
|
||||
STOP_LOSS_PCT=-5.0
|
||||
TAKE_PROFIT_PCT=10.0
|
||||
MONITOR_INTERVAL_MIN=30
|
||||
REPORTS_DIR=reports
|
||||
AUTO_REPORT_UPLOAD=true
|
||||
|
||||
# Optional bot model overrides
|
||||
DEEP_THINK_LLM=gemini-3-flash-preview
|
||||
QUICK_THINK_LLM=gemini-3-flash-preview
|
||||
MAX_DEBATE_ROUNDS=1
|
||||
```
|
||||
|
||||
### 설정 메모
|
||||
|
||||
- `DISCORD_CHANNEL_IDS`를 비워두면 수동 명령은 모든 채널에서 사용할 수 있습니다.
|
||||
- 자동매매 스케줄은 `DISCORD_CHANNEL_IDS`가 설정된 경우에만 실제로 동작합니다.
|
||||
- 미국 수동/자동 주문은 `ENABLE_US_TRADING=true`가 아니면 막힙니다.
|
||||
- `KIS_VIRTUAL=true`면 모의투자, `false`면 실전투자입니다.
|
||||
- `AUTO_BUY_BUDGET_RATIO=0.5`처럼 설정하면 KR 자동매수는 기준 자금의 50%만 사용합니다. `50%` 형식도 가능합니다.
|
||||
- `US_AUTO_BUY_BUDGET_RATIO`를 비워두면 미국 자동매수도 같은 비율을 사용합니다.
|
||||
- 기준 자금(anchor)은 시장별로 저장되는 자동매수 기준 예수금이며, 더 큰 예수금을 확인하면 자동으로 상향 갱신됩니다.
|
||||
- Discord 봇은 모델명만 환경변수로 덮어쓰고, 기본 provider는 [`tradingagents/default_config.py`](/home/devuser/projects/TradingAgents2/tradingagents/default_config.py) 설정을 따릅니다.
|
||||
|
||||
### KIS 앱키 발급
|
||||
|
||||
1. [한국투자증권 API 포털](https://apiportal.koreainvestment.com)에 로그인합니다.
|
||||
2. 앱키를 발급합니다.
|
||||
3. 모의투자와 실전투자는 앱키가 다릅니다.
|
||||
4. 계좌번호는 `12345678-01` 형식으로 입력합니다.
|
||||
|
||||
### Discord 봇 토큰 발급
|
||||
|
||||
1. [Discord Developer Portal](https://discord.com/developers/applications)에서 애플리케이션을 만듭니다.
|
||||
2. `Bot` 탭에서 토큰을 발급합니다.
|
||||
3. OAuth2에서 `bot`, `applications.commands` 스코프를 추가해 서버에 초대합니다.
|
||||
|
||||
---
|
||||
|
||||
## 사용법
|
||||
|
||||
### Discord 봇 실행
|
||||
|
||||
Launch the interactive CLI:
|
||||
```bash
|
||||
tradingagents # installed command
|
||||
python -m cli.main # alternative: run directly from source
|
||||
```
|
||||
You will see a screen where you can select your desired tickers, analysis date, LLM provider, research depth, and more.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/cli/cli_init.png" width="100%" style="display: inline-block; margin: 0 2%;">
|
||||
</p>
|
||||
|
||||
An interface will appear showing results as they load, letting you track the agent's progress as it runs.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/cli/cli_news.png" width="100%" style="display: inline-block; margin: 0 2%;">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/cli/cli_transaction.png" width="100%" style="display: inline-block; margin: 0 2%;">
|
||||
</p>
|
||||
|
||||
## TradingAgents Package
|
||||
|
||||
### Implementation Details
|
||||
|
||||
We built TradingAgents with LangGraph to ensure flexibility and modularity. The framework supports multiple LLM providers: OpenAI, Google, Anthropic, xAI, OpenRouter, and Ollama.
|
||||
|
||||
### Python Usage
|
||||
|
||||
To use TradingAgents inside your code, you can import the `tradingagents` module and initialize a `TradingAgentsGraph()` object. The `.propagate()` function will return a decision. You can run `main.py`, here's also a quick example:
|
||||
|
||||
```python
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
|
||||
ta = TradingAgentsGraph(debug=True, config=DEFAULT_CONFIG.copy())
|
||||
|
||||
# forward propagate
|
||||
_, decision = ta.propagate("NVDA", "2026-01-15")
|
||||
print(decision)
|
||||
python bot.py
|
||||
```
|
||||
|
||||
You can also adjust the default configuration to set your own choice of LLMs, debate rounds, etc.
|
||||
정상 실행 시 봇은 아래 정보를 콘솔에 출력합니다.
|
||||
|
||||
- 동기화된 슬래시 명령 수
|
||||
- KR/US 자동매매 시각
|
||||
- 손절/익절 기준
|
||||
- 허용 채널 여부
|
||||
- 모의/실전 모드
|
||||
|
||||
### 슬래시 명령어
|
||||
|
||||
현재 등록되는 명령은 11개입니다.
|
||||
|
||||
| 명령 | 설명 |
|
||||
|------|------|
|
||||
| `/분석 <티커> [날짜]` | 단일 종목 멀티 에이전트 AI 분석, 보고서 파일 첨부 |
|
||||
| `/대형주 [날짜]` | KR 스코어링 TOP5를 순차 분석하고 BUY 종목에 버튼 제공 |
|
||||
| `/잔고` | KRW/USD 계좌 요약과 보유 종목 조회 |
|
||||
| `/매수 <티커> [수량]` | 시장별 수동 예산 상한 기준 매수 확인 버튼 표시 |
|
||||
| `/매도 <티커> [수량]` | 보유 수량 기준 매도 확인 버튼 표시 |
|
||||
| `/상태` | 오늘 자동매매 실행 상태 조회 |
|
||||
| `/봇정보` | 스케줄, 설정, 계좌 요약, 오늘 이력을 한 번에 조회 |
|
||||
| `/스코어링 [시장] [count] [exclude_held]` | 실시간 후보 점수 조회 |
|
||||
| `/스코어규칙 [시장]` | 현재 코드 기준 스코어링 규칙 조회 |
|
||||
| `/수익` | 누적 실현손익, 승률, 종목별 요약 조회 |
|
||||
| `/수익초기화 [통화]` | 손익 집계 기준 시점 초기화 |
|
||||
|
||||
### 수동 주문 동작 방식
|
||||
|
||||
- `/매수`는 수량을 생략하면 `KIS_MAX_ORDER_AMOUNT` 또는 `US_MAX_ORDER_AMOUNT` 기준으로 자동 수량을 계산합니다.
|
||||
- `/매도`는 수량을 생략하면 전량 매도로 동작합니다.
|
||||
- 장이 닫혀 있으면 매수 버튼을 띄우지 않습니다.
|
||||
- 매수 확인 버튼은 5분, 매도 확인 버튼은 2분 뒤 만료됩니다.
|
||||
|
||||
### 자동매매 스케줄
|
||||
|
||||
자동매매는 허용 채널이 있을 때만 실행됩니다.
|
||||
|
||||
#### KR 자동매수
|
||||
|
||||
1. 워치리스트 점수 계산
|
||||
2. 보유 종목 제외
|
||||
3. 상위 `DAY_TRADE_PICKS`만 AI 분석
|
||||
4. BUY 종목만 `기준 자금(anchor) × AUTO_BUY_BUDGET_RATIO` 예산 안에서 균등분할 매수
|
||||
5. 장 시작 전 분석이 끝나면 개장까지 대기 후 주문
|
||||
|
||||
#### KR 오후 점검
|
||||
|
||||
1. 보유 종목 조회
|
||||
2. `KR_WATCHLIST` 밖 종목만 시장가 매도
|
||||
3. 워치리스트 종목은 유지
|
||||
4. 결과를 Discord와 손익 DB에 반영
|
||||
|
||||
#### US 자동매매
|
||||
|
||||
- `ENABLE_US_TRADING=true`일 때만 실행됩니다.
|
||||
- KR과 동일한 흐름으로 동작하되 시간대만 ET 기준이며, 예산은 `US_AUTO_BUY_BUDGET_RATIO`를 따릅니다.
|
||||
- 오후 점검도 `US_WATCHLIST` 밖 종목만 정리합니다.
|
||||
|
||||
#### 손절/익절 모니터링
|
||||
|
||||
- KR/US 보유 종목 전체를 감시합니다.
|
||||
- 손절 또는 익절 조건에 도달하면 확인 없이 자동 매도합니다.
|
||||
|
||||
### 분석 보고서
|
||||
|
||||
- 보고서는 `REPORTS_DIR` 아래 Markdown 파일로 저장됩니다.
|
||||
- `AUTO_REPORT_UPLOAD=true`면 자동매매 중 생성된 보고서도 Discord로 업로드합니다.
|
||||
|
||||
### Python에서 직접 사용
|
||||
|
||||
```python
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
|
||||
config = DEFAULT_CONFIG.copy()
|
||||
config["llm_provider"] = "openai" # openai, google, anthropic, xai, openrouter, ollama
|
||||
config["deep_think_llm"] = "gpt-5.2" # Model for complex reasoning
|
||||
config["quick_think_llm"] = "gpt-5-mini" # Model for quick tasks
|
||||
config["max_debate_rounds"] = 2
|
||||
config["deep_think_llm"] = "gemini-3-flash-preview"
|
||||
config["quick_think_llm"] = "gemini-3-flash-preview"
|
||||
|
||||
ta = TradingAgentsGraph(debug=True, config=config)
|
||||
_, decision = ta.propagate("NVDA", "2026-01-15")
|
||||
final_state, decision = ta.propagate("AAPL", "2026-03-18")
|
||||
print(decision)
|
||||
```
|
||||
|
||||
See `tradingagents/default_config.py` for all configuration options.
|
||||
### CLI 사용
|
||||
|
||||
## Contributing
|
||||
```bash
|
||||
python -m cli.main
|
||||
```
|
||||
|
||||
We welcome contributions from the community! Whether it's fixing a bug, improving documentation, or suggesting a new feature, your input helps make this project better. If you are interested in this line of research, please consider joining our open-source financial AI research community [Tauric Research](https://tauric.ai/).
|
||||
또는 설치 후:
|
||||
|
||||
```bash
|
||||
tradingagents
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## KIS API 연동
|
||||
|
||||
핵심 구현은 [`kis_client.py`](/home/devuser/projects/TradingAgents2/kis_client.py)에 있습니다.
|
||||
|
||||
### 현재 실제로 쓰는 API
|
||||
|
||||
| 기능 | 엔드포인트 | 비고 |
|
||||
|------|------------|------|
|
||||
| OAuth 토큰 | `POST /oauth2/tokenP` | 실전/모의 공통 |
|
||||
| KR 잔고 조회 | `GET /uapi/domestic-stock/v1/trading/inquire-balance` | KRW 요약 포함 |
|
||||
| US 잔고 조회 | `GET /uapi/overseas-stock/v1/trading/inquire-balance` | 거래소별 합산 |
|
||||
| KR 현재가 | `GET /uapi/domestic-stock/v1/quotations/inquire-price` | 국내 6자리 코드 |
|
||||
| US 현재가 | `GET /uapi/overseas-price/v1/quotations/price` | 거래소 탐색 포함 |
|
||||
| KR 주문 | `POST /uapi/domestic-stock/v1/trading/order-cash` | 시장가 매수/매도 |
|
||||
| US 주문 | `POST /uapi/overseas-stock/v1/trading/order` | 시장가 매수/매도 |
|
||||
| KR 시총 랭킹 | `GET /uapi/domestic-stock/v1/ranking/market-cap` | 스코어링 보너스 |
|
||||
| KR 거래량 랭킹 | `GET /uapi/domestic-stock/v1/quotations/volume-rank` | 스코어링 보너스 |
|
||||
| US 시총 랭킹 | `GET /uapi/overseas-stock/v1/ranking/market-cap` | 실전에서만 사용 |
|
||||
| US 거래량 랭킹 | `GET /uapi/overseas-stock/v1/ranking/trade-vol` | 실전에서만 사용 |
|
||||
|
||||
### 코드에 남아 있는 KR 보조 랭킹 유틸리티
|
||||
|
||||
현재 기본 전략은 사용하지 않지만, 아래 API 래퍼도 구현돼 있습니다.
|
||||
|
||||
- 체결강도 순위: `get_volume_power()`
|
||||
- 등락률 순위: `get_fluctuation_rank()`
|
||||
- 대량체결 순위: `get_bulk_trans()`
|
||||
|
||||
### 중요한 운영 차이
|
||||
|
||||
- KR 워치리스트가 비어 있으면 시총 랭킹, 그다음 거래량 랭킹으로 후보를 보완합니다.
|
||||
- US 워치리스트가 비어 있어도 같은 순서로 fallback 합니다.
|
||||
- 미국 시총/거래량 랭킹은 현재 코드에서 모의투자 시 빈 결과를 반환하도록 되어 있습니다.
|
||||
|
||||
---
|
||||
|
||||
## LLM과 데이터 소스
|
||||
|
||||
### 프레임워크가 지원하는 LLM 제공자
|
||||
|
||||
- OpenAI
|
||||
- Google
|
||||
- Anthropic
|
||||
- xAI
|
||||
- OpenRouter
|
||||
- Ollama
|
||||
|
||||
### 현재 Discord 봇 기본값
|
||||
|
||||
- provider 기본값: [`tradingagents/default_config.py`](/home/devuser/projects/TradingAgents2/tradingagents/default_config.py)
|
||||
- 봇 환경변수로 덮는 값: `DEEP_THINK_LLM`, `QUICK_THINK_LLM`, `MAX_DEBATE_ROUNDS`
|
||||
- 데이터 수집: yfinance
|
||||
- 주문/잔고/랭킹: KIS Open API
|
||||
|
||||
---
|
||||
|
||||
## 프로젝트 구조
|
||||
|
||||
```text
|
||||
TradingAgents/
|
||||
├── bot.py
|
||||
├── kis_client.py
|
||||
├── trade_history.py
|
||||
├── main.py
|
||||
├── README.md
|
||||
├── .env.example
|
||||
├── requirements.txt
|
||||
├── pyproject.toml
|
||||
├── reports/
|
||||
├── data/
|
||||
├── cli/
|
||||
└── tradingagents/
|
||||
├── agents/
|
||||
├── dataflows/
|
||||
├── graph/
|
||||
├── llm_clients/
|
||||
└── default_config.py
|
||||
```
|
||||
|
||||
주요 파일:
|
||||
|
||||
- [`bot.py`](/home/devuser/projects/TradingAgents2/bot.py): Discord 봇, 명령어, 자동매매 스케줄
|
||||
- [`kis_client.py`](/home/devuser/projects/TradingAgents2/kis_client.py): KIS REST API 래퍼
|
||||
- [`trade_history.py`](/home/devuser/projects/TradingAgents2/trade_history.py): SQLite 기반 매매/손익 기록
|
||||
- [`tradingagents/graph/trading_graph.py`](/home/devuser/projects/TradingAgents2/tradingagents/graph/trading_graph.py): 멀티 에이전트 분석 그래프
|
||||
|
||||
---
|
||||
|
||||
## 운영 메모
|
||||
|
||||
- 수동 주문은 예산 상한을 넘기면 차단됩니다.
|
||||
- 자동매매는 `daily_state` 기록으로 중복 실행을 막습니다.
|
||||
- 보고서는 저장 실패 시에도 Discord 전송을 가능한 형태로 fallback 합니다.
|
||||
- 분석은 `asyncio.Lock`으로 직렬화되어 동시에 여러 건이 돌지 않습니다.
|
||||
- 모의투자에서는 실전과 일부 API 응답 차이가 있을 수 있습니다.
|
||||
|
||||
> 이 프로젝트는 연구/자동화 실험용입니다. 실제 주문 전에는 반드시 모의투자로 충분히 검증하세요.
|
||||
|
||||
---
|
||||
|
||||
## Citation
|
||||
|
||||
Please reference our work if you find *TradingAgents* provides you with some help :)
|
||||
|
||||
```
|
||||
```bibtex
|
||||
@misc{xiao2025tradingagentsmultiagentsllmfinancial,
|
||||
title={TradingAgents: Multi-Agents LLM Financial Trading Framework},
|
||||
title={TradingAgents: Multi-Agents LLM Financial Trading Framework},
|
||||
author={Yijia Xiao and Edward Sun and Di Luo and Wei Wang},
|
||||
year={2025},
|
||||
eprint={2412.20138},
|
||||
archivePrefix={arXiv},
|
||||
primaryClass={q-fin.TR},
|
||||
url={https://arxiv.org/abs/2412.20138},
|
||||
url={https://arxiv.org/abs/2412.20138},
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
services:
|
||||
trading-bot:
|
||||
image: ${IMAGE_NAME:-ghcr.io/jjyn0215/tradingagents:latest}
|
||||
pull_policy: always
|
||||
container_name: tradingagents-bot
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- TZ=Asia/Seoul
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./results:/app/results
|
||||
- ./reports:/app/reports
|
||||
- ./eval_results:/app/eval_results
|
||||
command: ["python", "-u", "bot.py"]
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1 +1,23 @@
|
|||
.
|
||||
typing-extensions
|
||||
langchain-core
|
||||
langchain-openai
|
||||
langchain-experimental
|
||||
pandas
|
||||
yfinance
|
||||
stockstats
|
||||
langgraph
|
||||
rank-bm25
|
||||
setuptools
|
||||
backtrader
|
||||
parsel
|
||||
requests
|
||||
tqdm
|
||||
pytz
|
||||
redis
|
||||
chainlit
|
||||
rich
|
||||
typer
|
||||
questionary
|
||||
langchain_anthropic
|
||||
langchain-google-genai
|
||||
discord.py
|
||||
|
|
@ -0,0 +1,446 @@
|
|||
"""
|
||||
매매 이력 관리 (SQLite)
|
||||
- 매수/매도 기록 저장
|
||||
- 누적 수익률 조회
|
||||
- 통화별 수익 요약
|
||||
- 일일 상태 관리 (재시작 중복 방지)
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
DB_PATH = Path(__file__).parent / "data" / "trade_history.db"
|
||||
|
||||
|
||||
def _get_conn() -> sqlite3.Connection:
|
||||
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(str(DB_PATH))
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
return conn
|
||||
|
||||
|
||||
def _has_column(conn: sqlite3.Connection, table: str, column: str) -> bool:
|
||||
rows = conn.execute(f"PRAGMA table_info({table})").fetchall()
|
||||
return any(r["name"] == column for r in rows)
|
||||
|
||||
|
||||
def _migrate_schema(conn: sqlite3.Connection):
|
||||
"""기존 DB에 누락 컬럼을 idempotent하게 추가."""
|
||||
if not _has_column(conn, "trades", "market"):
|
||||
conn.execute("ALTER TABLE trades ADD COLUMN market TEXT NOT NULL DEFAULT 'KR'")
|
||||
if not _has_column(conn, "trades", "currency"):
|
||||
conn.execute("ALTER TABLE trades ADD COLUMN currency TEXT NOT NULL DEFAULT 'KRW'")
|
||||
if not _has_column(conn, "pnl_log", "market"):
|
||||
conn.execute("ALTER TABLE pnl_log ADD COLUMN market TEXT NOT NULL DEFAULT 'KR'")
|
||||
if not _has_column(conn, "pnl_log", "currency"):
|
||||
conn.execute("ALTER TABLE pnl_log ADD COLUMN currency TEXT NOT NULL DEFAULT 'KRW'")
|
||||
|
||||
|
||||
def init_db():
|
||||
"""테이블 생성 (최초 1회) + 스키마 마이그레이션."""
|
||||
conn = _get_conn()
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS trades (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ticker TEXT NOT NULL,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
side TEXT NOT NULL CHECK(side IN ('BUY', 'SELL')),
|
||||
qty INTEGER NOT NULL,
|
||||
price REAL NOT NULL,
|
||||
amount REAL NOT NULL,
|
||||
market TEXT NOT NULL DEFAULT 'KR',
|
||||
currency TEXT NOT NULL DEFAULT 'KRW',
|
||||
order_no TEXT DEFAULT '',
|
||||
reason TEXT DEFAULT '',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pnl_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ticker TEXT NOT NULL,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
buy_price REAL NOT NULL,
|
||||
sell_price REAL NOT NULL,
|
||||
qty INTEGER NOT NULL,
|
||||
pnl REAL NOT NULL,
|
||||
pnl_rate REAL NOT NULL,
|
||||
market TEXT NOT NULL DEFAULT 'KR',
|
||||
currency TEXT NOT NULL DEFAULT 'KRW',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS daily_state (
|
||||
date TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
completed_at TEXT NOT NULL,
|
||||
details TEXT DEFAULT '',
|
||||
PRIMARY KEY (date, action)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pnl_resets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
currency TEXT DEFAULT NULL,
|
||||
reset_by TEXT DEFAULT '',
|
||||
reason TEXT DEFAULT '',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS budget_anchor (
|
||||
market TEXT PRIMARY KEY,
|
||||
anchor_amount REAL NOT NULL DEFAULT 0,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
_migrate_schema(conn)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def record_trade(
|
||||
ticker: str,
|
||||
name: str,
|
||||
side: str,
|
||||
qty: int,
|
||||
price: float,
|
||||
order_no: str = "",
|
||||
reason: str = "",
|
||||
market: str = "KR",
|
||||
currency: str = "KRW",
|
||||
):
|
||||
"""매수/매도 기록 저장."""
|
||||
side = side.upper()
|
||||
market = (market or "KR").upper()
|
||||
currency = (currency or "KRW").upper()
|
||||
px = float(price)
|
||||
amount = float(qty) * px
|
||||
|
||||
conn = _get_conn()
|
||||
conn.execute(
|
||||
"""INSERT INTO trades
|
||||
(ticker, name, side, qty, price, amount, market, currency, order_no, reason)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(ticker, name, side, qty, px, amount, market, currency, order_no, reason),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def record_pnl(
|
||||
ticker: str,
|
||||
name: str,
|
||||
buy_price: float,
|
||||
sell_price: float,
|
||||
qty: int,
|
||||
market: str = "KR",
|
||||
currency: str = "KRW",
|
||||
):
|
||||
"""실현 손익 기록."""
|
||||
buy_px = float(buy_price)
|
||||
sell_px = float(sell_price)
|
||||
pnl = (sell_px - buy_px) * qty
|
||||
pnl_rate = ((sell_px - buy_px) / buy_px * 100) if buy_px > 0 else 0.0
|
||||
market = (market or "KR").upper()
|
||||
currency = (currency or "KRW").upper()
|
||||
|
||||
conn = _get_conn()
|
||||
conn.execute(
|
||||
"""INSERT INTO pnl_log
|
||||
(ticker, name, buy_price, sell_price, qty, pnl, pnl_rate, market, currency)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(ticker, name, buy_px, sell_px, qty, pnl, round(pnl_rate, 2), market, currency),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def _aggregate_pnl(
|
||||
market: str | None = None,
|
||||
currency: str | None = None,
|
||||
) -> dict:
|
||||
conn = _get_conn()
|
||||
where_clause, params = _build_pnl_where_clause(conn, market=market, currency=currency)
|
||||
row = conn.execute(
|
||||
f"""SELECT
|
||||
COALESCE(SUM(pnl), 0) as total_pnl,
|
||||
COUNT(*) as trade_count,
|
||||
COALESCE(SUM(CASE WHEN pnl > 0 THEN 1 ELSE 0 END), 0) as win_count,
|
||||
COALESCE(SUM(CASE WHEN pnl <= 0 THEN 1 ELSE 0 END), 0) as loss_count
|
||||
FROM pnl_log
|
||||
{where_clause}""",
|
||||
params,
|
||||
).fetchone()
|
||||
conn.close()
|
||||
|
||||
total = float(row["total_pnl"])
|
||||
count = int(row["trade_count"])
|
||||
win = int(row["win_count"])
|
||||
loss = int(row["loss_count"])
|
||||
win_rate = (win / count * 100) if count > 0 else 0.0
|
||||
|
||||
return {
|
||||
"total_pnl": total,
|
||||
"trade_count": count,
|
||||
"win_count": win,
|
||||
"loss_count": loss,
|
||||
"win_rate": round(win_rate, 1),
|
||||
}
|
||||
|
||||
|
||||
def _get_pnl_reset_cutoff(
|
||||
conn: sqlite3.Connection,
|
||||
currency: str | None = None,
|
||||
) -> str | None:
|
||||
target_currency = (currency or "").upper() or None
|
||||
if target_currency:
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT MAX(created_at) AS cutoff
|
||||
FROM pnl_resets
|
||||
WHERE currency IS NULL OR currency = ?
|
||||
""",
|
||||
(target_currency,),
|
||||
).fetchone()
|
||||
else:
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT MAX(created_at) AS cutoff
|
||||
FROM pnl_resets
|
||||
WHERE currency IS NULL
|
||||
"""
|
||||
).fetchone()
|
||||
|
||||
cutoff = row["cutoff"] if row else None
|
||||
return str(cutoff) if cutoff else None
|
||||
|
||||
|
||||
def _build_pnl_where_clause(
|
||||
conn: sqlite3.Connection,
|
||||
market: str | None = None,
|
||||
currency: str | None = None,
|
||||
) -> tuple[str, list[object]]:
|
||||
conditions: list[str] = []
|
||||
params: list[object] = []
|
||||
if market:
|
||||
conditions.append("market = ?")
|
||||
params.append(market.upper())
|
||||
if currency:
|
||||
conditions.append("currency = ?")
|
||||
params.append(currency.upper())
|
||||
|
||||
cutoff = _get_pnl_reset_cutoff(conn, currency=currency)
|
||||
if cutoff:
|
||||
conditions.append("created_at > ?")
|
||||
params.append(cutoff)
|
||||
|
||||
where_clause = f"WHERE {' AND '.join(conditions)}" if conditions else ""
|
||||
return where_clause, params
|
||||
|
||||
|
||||
def get_total_pnl(
|
||||
market: str | None = None,
|
||||
currency: str | None = None,
|
||||
) -> dict:
|
||||
"""누적 수익 요약 (기존 호환: 인자 없이 전체 반환)."""
|
||||
return _aggregate_pnl(market=market, currency=currency)
|
||||
|
||||
|
||||
def get_total_pnl_by_currency() -> dict[str, dict]:
|
||||
"""통화별 누적 수익 요약."""
|
||||
conn = _get_conn()
|
||||
rows = conn.execute(
|
||||
"SELECT DISTINCT currency FROM pnl_log ORDER BY currency"
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
currencies = [r["currency"] for r in rows] or ["KRW", "USD"]
|
||||
result: dict[str, dict] = {}
|
||||
for cur in currencies:
|
||||
result[cur] = _aggregate_pnl(currency=cur)
|
||||
return result
|
||||
|
||||
|
||||
def get_recent_trades(
|
||||
limit: int = 20,
|
||||
market: str | None = None,
|
||||
currency: str | None = None,
|
||||
) -> list[dict]:
|
||||
"""최근 매매 이력."""
|
||||
conn = _get_conn()
|
||||
conditions: list[str] = []
|
||||
params: list[object] = []
|
||||
if market:
|
||||
conditions.append("market = ?")
|
||||
params.append(market.upper())
|
||||
if currency:
|
||||
conditions.append("currency = ?")
|
||||
params.append(currency.upper())
|
||||
where_clause = f"WHERE {' AND '.join(conditions)}" if conditions else ""
|
||||
params.append(limit)
|
||||
rows = conn.execute(
|
||||
f"SELECT * FROM trades {where_clause} ORDER BY id DESC LIMIT ?", params
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def get_recent_pnl(
|
||||
limit: int = 20,
|
||||
market: str | None = None,
|
||||
currency: str | None = None,
|
||||
) -> list[dict]:
|
||||
"""최근 실현손익."""
|
||||
conn = _get_conn()
|
||||
where_clause, params = _build_pnl_where_clause(conn, market=market, currency=currency)
|
||||
params.append(limit)
|
||||
rows = conn.execute(
|
||||
f"SELECT * FROM pnl_log {where_clause} ORDER BY id DESC LIMIT ?", params
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def get_ticker_summary(
|
||||
market: str | None = None,
|
||||
currency: str | None = None,
|
||||
) -> list[dict]:
|
||||
"""종목별 누적 수익 요약."""
|
||||
conn = _get_conn()
|
||||
where_clause, params = _build_pnl_where_clause(conn, market=market, currency=currency)
|
||||
rows = conn.execute(
|
||||
f"""SELECT
|
||||
ticker, name, market, currency,
|
||||
COUNT(*) as count,
|
||||
SUM(pnl) as total_pnl,
|
||||
AVG(pnl_rate) as avg_pnl_rate
|
||||
FROM pnl_log
|
||||
{where_clause}
|
||||
GROUP BY ticker, name, market, currency
|
||||
ORDER BY total_pnl DESC""",
|
||||
params,
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def reset_pnl_history(
|
||||
currency: str | None = None,
|
||||
reset_by: str = "",
|
||||
reason: str = "",
|
||||
) -> str:
|
||||
"""실현손익 집계 기준 시점을 기록한다.
|
||||
|
||||
기존 손익 로그는 보존하고, 이후 조회 시 마지막 초기화 시점 이후 데이터만 집계한다.
|
||||
"""
|
||||
target_currency = (currency or "").upper() or None
|
||||
reset_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
conn = _get_conn()
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO pnl_resets (currency, reset_by, reason, created_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""",
|
||||
(target_currency, reset_by, reason, reset_at),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return reset_at
|
||||
|
||||
|
||||
# ─── 일일 상태 관리 (재시작 중복 방지) ─────────────────────
|
||||
def is_action_done(action: str, date: str | None = None) -> bool:
|
||||
"""오늘 해당 액션이 이미 완료되었는지 확인.
|
||||
|
||||
Args:
|
||||
action: 'morning_buy', 'afternoon_sell', 'us_morning_buy' 등
|
||||
date: 날짜 (기본: 오늘)
|
||||
"""
|
||||
if date is None:
|
||||
date = datetime.date.today().isoformat()
|
||||
conn = _get_conn()
|
||||
row = conn.execute(
|
||||
"SELECT 1 FROM daily_state WHERE date = ? AND action = ?",
|
||||
(date, action),
|
||||
).fetchone()
|
||||
conn.close()
|
||||
return row is not None
|
||||
|
||||
|
||||
def mark_action_done(action: str, details: str = "", date: str | None = None):
|
||||
"""해당 액션을 완료로 표시."""
|
||||
if date is None:
|
||||
date = datetime.date.today().isoformat()
|
||||
now = datetime.datetime.now().isoformat()
|
||||
conn = _get_conn()
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO daily_state (date, action, completed_at, details) VALUES (?, ?, ?, ?)",
|
||||
(date, action, now, details),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_daily_state(date: str | None = None) -> list[dict]:
|
||||
"""오늘 완료된 모든 액션 조회."""
|
||||
if date is None:
|
||||
date = datetime.date.today().isoformat()
|
||||
conn = _get_conn()
|
||||
rows = conn.execute(
|
||||
"SELECT action, completed_at, details FROM daily_state WHERE date = ? ORDER BY completed_at",
|
||||
(date,),
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def get_budget_anchor(market: str = "KR") -> float:
|
||||
"""시장별 자동매수 기준 자금(anchor) 조회."""
|
||||
conn = _get_conn()
|
||||
row = conn.execute(
|
||||
"SELECT anchor_amount FROM budget_anchor WHERE market = ?",
|
||||
((market or "KR").upper(),),
|
||||
).fetchone()
|
||||
conn.close()
|
||||
return float(row["anchor_amount"]) if row else 0.0
|
||||
|
||||
|
||||
def set_budget_anchor(market: str, anchor_amount: float) -> float:
|
||||
"""시장별 자동매수 기준 자금을 저장한다."""
|
||||
market = (market or "KR").upper()
|
||||
amount = max(float(anchor_amount), 0.0)
|
||||
now = datetime.datetime.now().isoformat()
|
||||
|
||||
conn = _get_conn()
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO budget_anchor (market, anchor_amount, updated_at)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(market) DO UPDATE SET
|
||||
anchor_amount = excluded.anchor_amount,
|
||||
updated_at = excluded.updated_at
|
||||
""",
|
||||
(market, amount, now),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return amount
|
||||
|
||||
|
||||
def ensure_budget_anchor(market: str, available_cash: float) -> float:
|
||||
"""기준 자금이 없으면 현재 예수금으로 초기화하고, 더 큰 값이 들어오면 상향 반영한다."""
|
||||
market = (market or "KR").upper()
|
||||
cash = max(float(available_cash), 0.0)
|
||||
current = get_budget_anchor(market)
|
||||
|
||||
if cash > 0 and (current <= 0 or cash > current):
|
||||
return set_budget_anchor(market, cash)
|
||||
return current
|
||||
|
||||
|
||||
# 모듈 로드 시 DB 초기화
|
||||
init_db()
|
||||
Loading…
Reference in New Issue