Files
auto-stock/backend/app/models/models.py

315 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
SQLAlchemy ORM 모델 정의
- StockDailyPrice: 일별 주가 데이터
- Portfolio: 포트폴리오 보유 종목
- TradeHistory: 주문/체결 이력
- TradingRule: 매매 규칙 설정
"""
from datetime import datetime, date
from typing import Optional, List
from sqlalchemy import Column, Integer, BigInteger, String, Numeric, Boolean, DateTime, Text, ARRAY, Date, ForeignKey, UniqueConstraint
from sqlalchemy.dialects.postgresql import JSONB
from app.core.database import Base
class StockDailyPrice(Base):
"""KRX 일봉 히스토리컬 데이터 (pykrx 수집)"""
__tablename__ = "stock_daily_prices"
__table_args__ = (
UniqueConstraint('stock_code', 'trade_date', name='uq_stock_daily'),
)
id = Column(Integer, primary_key=True, index=True)
stock_code = Column(String(20), nullable=False, index=True)
stock_name = Column(String(100))
trade_date = Column(Date, nullable=False, index=True)
open_price = Column(Integer)
high_price = Column(Integer)
low_price = Column(Integer)
close_price = Column(Integer)
volume = Column(BigInteger)
change_rate = Column(Numeric(10, 4))
market_cap = Column(BigInteger, nullable=True)
class TradingSession(Base):
"""🔥 일일 트레이딩 세션 관리 (리스크 관리)"""
__tablename__ = "trading_sessions"
id = Column(Integer, primary_key=True, index=True)
session_date = Column(Date, default=date.today, unique=True, index=True)
# 연속 손실 추적
consecutive_losses = Column(Integer, default=0)
consecutive_wins = Column(Integer, default=0)
# 일일 손익
daily_profit_loss = Column(Numeric(15, 2), default=0)
daily_trades_count = Column(Integer, default=0)
# 리스크 제한 상태
is_paused = Column(Boolean, default=False) # 3연패 또는 일일 손실 한도 도달 시 True
pause_reason = Column(String(200), nullable=True)
# 계좌 정보
starting_balance = Column(Numeric(15, 2))
ending_balance = Column(Numeric(15, 2))
peak_balance = Column(Numeric(15, 2)) # 당일 최고 잔고
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class TradingRule(Base):
__tablename__ = "trading_rules"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(255), nullable=False)
description = Column(Text)
rule_type = Column(String(50), nullable=False)
conditions = Column(JSONB, nullable=False)
actions = Column(JSONB, nullable=False)
is_active = Column(Boolean, default=True)
priority = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class TradeHistory(Base):
__tablename__ = "trade_history"
id = Column(Integer, primary_key=True, index=True)
rule_id = Column(Integer)
stock_code = Column(String(20), nullable=False, index=True)
stock_name = Column(String(100))
order_type = Column(String(20), nullable=False)
order_price = Column(Numeric(15, 2))
order_quantity = Column(Integer, nullable=False)
executed_price = Column(Numeric(15, 2))
executed_quantity = Column(Integer)
total_amount = Column(Numeric(15, 2)) # 총 거래 금액
status = Column(String(50), nullable=False)
order_id = Column(String(100))
message = Column(Text)
entry_reason = Column(Text) # 진입 이유 (매수 신호 상세)
exit_reason = Column(Text) # 매도 이유 (매도 신호 상세)
signals = Column(JSONB) # 기술적 신호 목록
# 🔥 리스크 관리 필드 추가
entry_trade_id = Column(Integer, nullable=True) # 매도 시 얰결된 매수 거래 ID
slippage = Column(Numeric(15, 2), nullable=True) # 슬리피지 (체결가 - 주문가)
commission = Column(Numeric(15, 2), nullable=True) # 수수료
net_profit_loss = Column(Numeric(15, 2), nullable=True) # 수수료 차감 후 순손익
profit_loss_rate = Column(Numeric(10, 4), nullable=True) # 수익률 (%)
created_at = Column(DateTime, default=datetime.utcnow, index=True)
executed_at = Column(DateTime)
class Portfolio(Base):
__tablename__ = "portfolio"
id = Column(Integer, primary_key=True, index=True)
stock_code = Column(String(20), nullable=False, unique=True)
stock_name = Column(String(100))
quantity = Column(Integer, nullable=False, default=0)
average_price = Column(Numeric(15, 2))
current_price = Column(Numeric(15, 2))
profit_loss = Column(Numeric(15, 2))
profit_loss_rate = Column(Numeric(10, 4))
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class MarketData(Base):
__tablename__ = "market_data"
id = Column(Integer, primary_key=True, index=True)
stock_code = Column(String(20), nullable=False, index=True)
price = Column(Numeric(15, 2), nullable=False)
change_rate = Column(Numeric(10, 4))
# real‑time volume may also exceed int32; switch to bigint for safety
volume = Column(BigInteger)
high_price = Column(Numeric(15, 2))
low_price = Column(Numeric(15, 2))
open_price = Column(Numeric(15, 2))
timestamp = Column(DateTime, default=datetime.utcnow, index=True)
class News(Base):
__tablename__ = "news"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(500), nullable=False)
content = Column(Text)
source = Column(String(100))
url = Column(Text)
stock_codes = Column(ARRAY(String))
sentiment = Column(String(20))
published_at = Column(DateTime, index=True)
created_at = Column(DateTime, default=datetime.utcnow)
class ClaudeAnalysisLog(Base):
__tablename__ = "claude_analysis_log"
id = Column(Integer, primary_key=True, index=True)
rule_id = Column(Integer)
stock_code = Column(String(20))
analysis_type = Column(String(50))
input_data = Column(JSONB)
analysis_result = Column(JSONB)
decision = Column(String(50))
confidence = Column(Numeric(5, 4))
created_at = Column(DateTime, default=datetime.utcnow, index=True)
class MCPToolCall(Base):
__tablename__ = "mcp_tool_calls"
id = Column(Integer, primary_key=True, index=True)
tool_name = Column(String(100), nullable=False)
parameters = Column(JSONB)
result = Column(JSONB)
status = Column(String(50))
execution_time_ms = Column(Integer)
created_at = Column(DateTime, default=datetime.utcnow, index=True)
class StockAnalysis(Base):
"""매일 배치로 저장되는 종목 분석 데이터"""
__tablename__ = "stock_analysis"
__table_args__ = (
UniqueConstraint("stock_code", "analysis_date", name="uq_stock_analysis_code_date"),
)
id = Column(Integer, primary_key=True, index=True)
stock_code = Column(String(20), nullable=False, index=True)
stock_name = Column(String(100), nullable=False)
analysis_date = Column(Date, nullable=False, index=True)
# 가격 정보
current_price = Column(Integer, nullable=False)
change_rate = Column(Numeric(10, 4))
# volumes can exceed 32‑bit limits (e.g. 3 billion+), use bigint to avoid overflow
volume = Column(BigInteger)
# 기술적 지표
rsi = Column(Numeric(10, 4))
ma5 = Column(Numeric(15, 2))
ma20 = Column(Numeric(15, 2))
ma60 = Column(Numeric(15, 2))
stochastic_k = Column(Numeric(10, 4))
stochastic_d = Column(Numeric(10, 4))
bollinger_upper = Column(Numeric(15, 2))
bollinger_lower = Column(Numeric(15, 2))
bollinger_middle = Column(Numeric(15, 2))
# 분석 결과
signals = Column(JSONB) # ["RSI 과매도", "골든크로스" 등]
score = Column(Integer, default=0)
# ATR (섹션 6.2 ATR 기반 손절)
atr_14 = Column(Numeric(15, 2), nullable=True)
# ADX (추세 강도 — 평균회귀 필터용)
adx_14 = Column(Numeric(10, 2), nullable=True)
# ML 앙상블 스코어
ml_score = Column(Integer, nullable=True) # 앙상블 스코어 (0~100)
ml_confidence = Column(Numeric(5, 4), nullable=True) # 신뢰도 (0~1)
ml_signal = Column(String(10), nullable=True) # BUY/HOLD/SELL
# 메타데이터
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class ActiveTrade(Base):
"""현재 활성화된(보유 중인) 거래를 추적하기 위한 모델"""
__tablename__ = "active_trades"
id = Column(Integer, primary_key=True, index=True)
stock_code = Column(String(20), nullable=False)
stock_name = Column(String(100))
quantity = Column(Integer, nullable=False)
entry_price = Column(Numeric(15, 2), nullable=False)
current_price = Column(Numeric(15, 2))
profit_loss = Column(Numeric(15, 2))
profit_loss_rate = Column(Numeric(10, 4))
stop_loss = Column(Numeric(15, 2))
take_profit = Column(Numeric(15, 2))
order_id = Column(String(100))
entry_reason = Column(Text)
status = Column(String(20), default="active") # active, closed
# 분할 청산 지원 (섹션 6.2, 7.3)
remaining_pct = Column(Numeric(5, 2), default=1.0) # 잔여 비율 (1.0=전량, 0.5=절반 청산 후)
highest_since_entry = Column(Numeric(15, 2), nullable=True) # 진입 후 최고가 (트레일링 스탑용)
strategy_name = Column(String(100), nullable=True) # 전략명 (MEAN_REVERSION 등)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class TradeJournal(Base):
__tablename__ = "trade_journals"
id = Column(Integer, primary_key=True, index=True)
trade_id = Column(Integer) # ForeignKey("trade_history.id") - models.py 내 순환 참조 방지 위해 정수로 정의
stock_code = Column(String(20), nullable=False, index=True)
stock_name = Column(String(100))
journal_date = Column(DateTime, default=datetime.utcnow)
content = Column(Text)
strategy_name = Column(String(100))
mood = Column(String(50))
tags = Column(JSONB)
screenshot_url = Column(Text)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class StockBan(Base):
"""종목별 연속 손실 금지 기록 (CLAUDE.md 섹션 7.4)
재시작 후에도 ban 상태 유지를 위해 DB에 영속화.
"""
__tablename__ = "stock_bans"
stock_code = Column(String(20), primary_key=True)
consecutive_losses = Column(Integer, default=0, nullable=False)
ban_until = Column(DateTime, nullable=True) # None이면 금지 아님
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class WatchlistStock(Base):
"""분석종목 — 장중 미니배치가 관리하는 활성 관심 종목.
아침 배치(batch_daily_analysis)가 score 상위 50개로 초기화하고,
장중 미니배치(batch_intraday_analysis)가 10분마다 진입/제거를 관리한다.
"""
__tablename__ = "watchlist_stocks"
id = Column(Integer, primary_key=True, index=True)
stock_code = Column(String(20), nullable=False, index=True)
stock_name = Column(String(100), nullable=False)
added_at = Column(DateTime, nullable=False)
added_reason = Column(String(50), nullable=False, default="morning_batch")
score_at_add = Column(Integer, nullable=True)
is_active = Column(Boolean, default=True, nullable=False, index=True)
removed_at = Column(DateTime, nullable=True)
removed_reason = Column(String(50), nullable=True)
class WatchlistHistory(Base):
"""분석종목 스코어 히스토리 — 10분마다 적재되는 시계열 데이터."""
__tablename__ = "watchlist_history"
id = Column(Integer, primary_key=True, index=True)
stock_code = Column(String(20), nullable=False, index=True)
stock_name = Column(String(100), nullable=False)
recorded_at = Column(DateTime, nullable=False, index=True)
score = Column(Integer, nullable=True)
current_price = Column(Numeric(15, 2), nullable=True)
change_rate = Column(Numeric(10, 4), nullable=True)
rsi = Column(Numeric(10, 4), nullable=True)
signals = Column(JSONB, nullable=True)