주식 트레이드 강화 전략 추가
This commit is contained in:
@@ -18,179 +18,369 @@ def get_predictor():
|
||||
f" | AMP: {_lstm_predictor.use_amp}")
|
||||
return _lstm_predictor
|
||||
|
||||
def analyze_stock_process(ticker, prices, news_items, investor_trend=None):
|
||||
|
||||
def calculate_position_size(total_capital, current_price, volatility, score, ai_confidence,
|
||||
max_per_stock=3000000):
|
||||
"""
|
||||
[CPU Intensive] 기술적 분석 및 AI 판단을 수행하는 함수
|
||||
(ProcessPoolExecutor에서 실행됨)
|
||||
[v2.0] 변동성 기반 포지션 사이징 (Modified Kelly Criterion)
|
||||
|
||||
핵심 원칙:
|
||||
1. 변동성이 높으면 → 적은 수량 (리스크 관리)
|
||||
2. 확신도(score)가 높으면 → 많은 수량 (기회 포착)
|
||||
3. AI 신뢰도가 높으면 → 가산 비중
|
||||
4. 절대 한 종목에 전체 자산의 15% 이상 투자하지 않음
|
||||
|
||||
Returns:
|
||||
int: 매수 수량 (0이면 매수 안 함)
|
||||
"""
|
||||
if current_price <= 0 or total_capital <= 0:
|
||||
return 0
|
||||
|
||||
# 1. 기본 투자금 (전체 자산의 10%)
|
||||
base_invest = total_capital * 0.10
|
||||
|
||||
# 2. 변동성 조절 계수 (변동성 높을수록 투자금 감소)
|
||||
# 변동성 1% → 1.0배, 2% → 0.75배, 3% → 0.5배, 5%+ → 0.3배
|
||||
if volatility <= 1.0:
|
||||
vol_factor = 1.2 # 안정적 종목은 약간 증가
|
||||
elif volatility <= 2.0:
|
||||
vol_factor = 1.0
|
||||
elif volatility <= 3.0:
|
||||
vol_factor = 0.7
|
||||
elif volatility <= 5.0:
|
||||
vol_factor = 0.45
|
||||
else:
|
||||
vol_factor = 0.3 # 고변동 종목
|
||||
|
||||
# 3. 확신도 조절 계수 (score가 높을수록 투자금 증가)
|
||||
# score 0.6 → 0.5배, 0.7 → 1.0배, 0.8 → 1.5배, 0.9+ → 2.0배
|
||||
if score >= 0.85:
|
||||
conf_factor = 2.0
|
||||
elif score >= 0.75:
|
||||
conf_factor = 1.5
|
||||
elif score >= 0.65:
|
||||
conf_factor = 1.0
|
||||
else:
|
||||
conf_factor = 0.5
|
||||
|
||||
# 4. AI 신뢰도 가산
|
||||
ai_bonus = 1.0
|
||||
if ai_confidence >= 0.85:
|
||||
ai_bonus = 1.3
|
||||
elif ai_confidence >= 0.7:
|
||||
ai_bonus = 1.1
|
||||
|
||||
# 5. 최종 투자금 계산
|
||||
invest_amount = base_invest * vol_factor * conf_factor * ai_bonus
|
||||
|
||||
# 상한 제한
|
||||
invest_amount = min(invest_amount, max_per_stock)
|
||||
invest_amount = min(invest_amount, total_capital * 0.15) # 최대 15%
|
||||
invest_amount = min(invest_amount, total_capital) # 잔고 초과 방지
|
||||
|
||||
# 수량 계산
|
||||
qty = int(invest_amount / current_price)
|
||||
return max(0, qty)
|
||||
|
||||
|
||||
def analyze_stock_process(ticker, prices, news_items, investor_trend=None,
|
||||
macro_status=None, holding_info=None):
|
||||
"""
|
||||
[v2.0] 종목 분석 + 매매 판단 (ProcessPoolExecutor에서 실행)
|
||||
|
||||
[v2.0 개선사항]
|
||||
1. ATR 기반 동적 손절/익절 + 트레일링 스탑
|
||||
2. 포지션 사이징 (변동성 + 확신도 기반)
|
||||
3. 시장상황별 동적 매수/매도 임계값
|
||||
4. 보유종목에 대한 분석 기반 매도 판단
|
||||
5. ADX/OBV/MTF 통합 기술적 분석
|
||||
6. 강화된 AI 프롬프트 (종목 고유 뉴스 분석)
|
||||
"""
|
||||
try:
|
||||
print(f"⚙️ [Bot Process] Analyzing {ticker} ({len(prices)} candles)...")
|
||||
|
||||
# 1. 기술적 지표 계산
|
||||
|
||||
# ===== 1. 기술적 지표 계산 =====
|
||||
current_price = prices[-1] if prices else 0
|
||||
# [수정] 변동성, 거래량 비율, MA 정보 반환
|
||||
tech_score, rsi, volatility, vol_ratio, ma_info = TechnicalAnalyzer.get_technical_score(current_price, prices, volume_history=None)
|
||||
|
||||
# 2. LSTM 주가 예측
|
||||
# [최적화] 전역 캐시된 Predictor 사용
|
||||
tech_score, rsi, volatility, vol_ratio, ma_info = TechnicalAnalyzer.get_technical_score(
|
||||
current_price, prices, volume_history=None)
|
||||
|
||||
# ===== 2. ATR 기반 동적 손절/익절 =====
|
||||
sl_tp = TechnicalAnalyzer.calculate_dynamic_sl_tp(prices)
|
||||
|
||||
# ===== 3. LSTM 주가 예측 =====
|
||||
lstm_predictor = get_predictor()
|
||||
if lstm_predictor:
|
||||
lstm_predictor.training_status['current_ticker'] = ticker
|
||||
pred_result = lstm_predictor.train_and_predict(prices, ticker=ticker)
|
||||
|
||||
lstm_score = 0.5 # 중립
|
||||
|
||||
lstm_score = 0.5
|
||||
ai_confidence = 0.5
|
||||
ai_loss = 1.0
|
||||
|
||||
if pred_result:
|
||||
ai_confidence = pred_result.get('confidence', 0.5)
|
||||
ai_loss = pred_result.get('loss', 1.0)
|
||||
|
||||
# 상승/하락 예측에 따라 점수 조정 (신뢰도 반영)
|
||||
# 최대 5% 변동폭까지 반영
|
||||
change_magnitude = min(abs(pred_result['change_rate']), 5.0) / 5.0
|
||||
|
||||
|
||||
change_magnitude = min(abs(pred_result['change_rate']), 5.0) / 5.0
|
||||
|
||||
if pred_result['trend'] == 'UP':
|
||||
# 상승 예측 시: 기본 0.5 + (강도 * 신뢰도 * 0.4) -> 최대 0.9
|
||||
lstm_score = 0.5 + (change_magnitude * ai_confidence * 0.4)
|
||||
else:
|
||||
# 하락 예측 시: 기본 0.5 - (강도 * 신뢰도 * 0.4) -> 최소 0.1
|
||||
lstm_score = 0.5 - (change_magnitude * ai_confidence * 0.4)
|
||||
|
||||
|
||||
lstm_score = max(0.0, min(1.0, lstm_score))
|
||||
|
||||
# [신규] 수급 분석 (외인/기관)
|
||||
# ===== 4. 수급 분석 (외인/기관) =====
|
||||
investor_score = 0.0
|
||||
frgn_net_buy = 0
|
||||
orgn_net_buy = 0
|
||||
consecutive_frgn_buy = 0
|
||||
|
||||
consecutive_orgn_buy = 0
|
||||
|
||||
if investor_trend:
|
||||
# 최근 5일 합산
|
||||
for day in investor_trend:
|
||||
frgn_net_buy += day['foreigner']
|
||||
orgn_net_buy += day['institutional']
|
||||
if day['foreigner'] > 0:
|
||||
consecutive_frgn_buy += 1
|
||||
|
||||
# 외인 수급 점수 (단순화)
|
||||
if frgn_net_buy > 0:
|
||||
investor_score += 0.05
|
||||
if consecutive_frgn_buy >= 3:
|
||||
investor_score += 0.05
|
||||
|
||||
if investor_score > 0:
|
||||
print(f" 💰 [Investor] Foreign Buy Detected (Net: {frgn_net_buy})")
|
||||
if day['institutional'] > 0:
|
||||
consecutive_orgn_buy += 1
|
||||
|
||||
# 3. AI 뉴스 분석
|
||||
# pred_result가 None일 경우 기본값 사용
|
||||
# 외인 수급 점수 (강화)
|
||||
if frgn_net_buy > 0:
|
||||
investor_score += 0.03
|
||||
if consecutive_frgn_buy >= 3:
|
||||
investor_score += 0.04
|
||||
if consecutive_frgn_buy >= 5:
|
||||
investor_score += 0.03 # 5일 연속 매수 = 추가 보너스
|
||||
|
||||
# 기관 수급 점수 (신규)
|
||||
if orgn_net_buy > 0:
|
||||
investor_score += 0.02
|
||||
if consecutive_orgn_buy >= 3:
|
||||
investor_score += 0.03
|
||||
|
||||
# 외인+기관 동시 순매수 = 강력 신호
|
||||
if frgn_net_buy > 0 and orgn_net_buy > 0:
|
||||
investor_score += 0.03
|
||||
print(f" 💰 [Investor] Both Foreign & Institutional Buying!")
|
||||
|
||||
# ===== 5. AI 뉴스 분석 (강화된 프롬프트) =====
|
||||
if pred_result:
|
||||
pred_price = pred_result.get('predicted', 0)
|
||||
pred_change = pred_result.get('change_rate', 0)
|
||||
else:
|
||||
pred_price = current_price
|
||||
pred_change = 0.0
|
||||
|
||||
ollama = OllamaManager()
|
||||
|
||||
ollama = OllamaManager()
|
||||
prompt = f"""
|
||||
[System Instruction]
|
||||
1. Role: You are a Expert Quant Trader with 20 years of experience.
|
||||
2. Market Data:
|
||||
- Technical Score: {tech_score:.2f} (RSI: {rsi:.2f})
|
||||
- Moving Average: {ma_info['trend']} (Price is {ma_info['position']})
|
||||
- AI Prediction: {pred_price:.0f} KRW ({pred_change}%)
|
||||
- AI Confidence: {ai_confidence:.2f} (Loss: {ai_loss:.4f})
|
||||
- Investor Trend (5 Days): Foreigner Net Buy {frgn_net_buy}, Institutional Net Buy {orgn_net_buy}
|
||||
3. Strategy:
|
||||
- If Foreigners are buying AND Trend is UP -> Strong BUY.
|
||||
- If AI Confidence > 0.8 and Trend is UP -> Strong BUY.
|
||||
- If MA is Bullish (Golden Alignment) -> Positive Signal.
|
||||
- If Price is above MA20 -> Support Uptrend.
|
||||
- If Trend is DOWN -> SELL/AVOID.
|
||||
4. Task: Analyze the news and combine with market data to decide sentiment.
|
||||
|
||||
News Data: {json.dumps(news_items, ensure_ascii=False)}
|
||||
|
||||
Response (JSON):
|
||||
{{
|
||||
"sentiment_score": 0.8,
|
||||
"reason": "Foreigners buying and Golden Cross detected."
|
||||
}}
|
||||
"""
|
||||
[System Instruction]
|
||||
1. Role: You are a legendary quant trader with 30 years of experience in Korean stock market.
|
||||
2. You MUST analyze the data objectively and respond with a JSON object.
|
||||
|
||||
[Market Data for Stock {ticker}]
|
||||
- Current Price: {current_price:,.0f} KRW
|
||||
- Technical Score: {tech_score:.3f} (RSI: {rsi:.1f})
|
||||
- Moving Average: {ma_info['trend']} (Price is {ma_info['position']})
|
||||
- ADX Trend Strength: {ma_info.get('adx', 20):.1f} ({ma_info.get('adx_trend', 'N/A')})
|
||||
- Multi-Timeframe: {ma_info.get('mtf_alignment', 'N/A')}
|
||||
- AI Prediction: {pred_price:.0f} KRW ({pred_change:+.2f}%)
|
||||
- AI Confidence: {ai_confidence:.2f} (Training Loss: {ai_loss:.4f})
|
||||
- Volatility: {volatility:.2f}%
|
||||
- Volume Ratio: {vol_ratio:.1f}x
|
||||
- ATR Stop Loss: {sl_tp['stop_loss_pct']:.1f}% / Take Profit: {sl_tp['take_profit_pct']:.1f}%
|
||||
- Investor Trend (5 Days): Foreigner Net Buy {frgn_net_buy}, Institutional Net Buy {orgn_net_buy}
|
||||
|
||||
[Decision Framework]
|
||||
- Strong BUY signals: Foreigners+Institutions buying, Golden Cross, ADX>25 with bullish trend, AI high confidence UP
|
||||
- Moderate BUY: RSI<40 with bullish reversal, Price near Bollinger Lower Band
|
||||
- SELL signals: RSI>70, Dead Cross, ADX>25 with bearish trend, Foreigners selling
|
||||
- AVOID/HOLD: ADX<20 (sideways), Mixed signals, Low confidence
|
||||
|
||||
[News Data]
|
||||
{json.dumps(news_items[:5] if news_items else [], ensure_ascii=False)}
|
||||
|
||||
[Response Format - JSON Only]
|
||||
{{"sentiment_score": 0.0 to 1.0, "reason": "Brief analysis reason"}}
|
||||
"""
|
||||
ai_resp = ollama.request_inference(prompt)
|
||||
sentiment_score = 0.5
|
||||
ai_reason = ""
|
||||
try:
|
||||
data = json.loads(ai_resp)
|
||||
sentiment_score = float(data.get("sentiment_score", 0.5))
|
||||
except:
|
||||
pass
|
||||
|
||||
# 4. 통합 점수 (동적 가중치)
|
||||
# AI 신뢰도가 높으면 AI 비중을 대폭 상향
|
||||
if ai_confidence >= 0.85:
|
||||
w_tech, w_news, w_ai = 0.2, 0.2, 0.6
|
||||
print(f" 🤖 [High Confidence] AI Weight Boosted to 60%")
|
||||
sentiment_score = max(0.0, min(1.0, sentiment_score)) # 범위 강제
|
||||
ai_reason = data.get("reason", "")
|
||||
except Exception:
|
||||
print(f" ⚠️ AI response parse failed, using neutral (0.5)")
|
||||
|
||||
# ===== 6. 통합 점수 (동적 가중치 v2.0) =====
|
||||
# ADX가 높으면 (추세가 강하면) LSTM과 기술적 분석 비중 증가
|
||||
adx_val = ma_info.get('adx', 20)
|
||||
|
||||
if ai_confidence >= 0.85 and adx_val >= 25:
|
||||
# 강한 추세 + 높은 AI 신뢰도: AI 최우선
|
||||
w_tech, w_news, w_ai = 0.15, 0.15, 0.70
|
||||
print(f" 🤖 [Ultra High Confidence + Strong Trend] AI Weight 70%")
|
||||
elif ai_confidence >= 0.85:
|
||||
w_tech, w_news, w_ai = 0.20, 0.20, 0.60
|
||||
print(f" 🤖 [High Confidence] AI Weight 60%")
|
||||
elif adx_val >= 30:
|
||||
# 매우 강한 추세: 기술적 분석 우선
|
||||
w_tech, w_news, w_ai = 0.50, 0.20, 0.30
|
||||
print(f" 📊 [Very Strong Trend ADX={adx_val:.0f}] Tech Weight 50%")
|
||||
elif adx_val < 20:
|
||||
# 비추세/횡보: 뉴스와 수급 중시
|
||||
w_tech, w_news, w_ai = 0.30, 0.40, 0.30
|
||||
print(f" 📰 [Sideways ADX={adx_val:.0f}] News Weight 40%")
|
||||
else:
|
||||
w_tech, w_news, w_ai = 0.4, 0.3, 0.3
|
||||
|
||||
w_tech, w_news, w_ai = 0.35, 0.30, 0.35
|
||||
|
||||
total_score = (w_tech * tech_score) + (w_news * sentiment_score) + (w_ai * lstm_score)
|
||||
|
||||
# [수신] 수급 가산점 추가 (최대 +0.1)
|
||||
total_score += investor_score
|
||||
|
||||
# 수급 가산점 (최대 +0.15)
|
||||
total_score += min(investor_score, 0.15)
|
||||
total_score = min(total_score, 1.0)
|
||||
|
||||
|
||||
# ===== 7. 시장 상황별 동적 임계값 =====
|
||||
buy_threshold = 0.60
|
||||
sell_threshold = 0.30
|
||||
|
||||
if macro_status:
|
||||
macro_state = macro_status.get('status', 'SAFE')
|
||||
if macro_state == 'DANGER':
|
||||
buy_threshold = 999.0 # 매수 완전 차단
|
||||
sell_threshold = 0.45 # 매도 기준 상향 (빨리 탈출)
|
||||
print(f" 🚨 [DANGER Market] Buy BLOCKED, Sell threshold raised to 0.45")
|
||||
elif macro_state == 'CAUTION':
|
||||
buy_threshold = 0.72 # 매수 기준 대폭 상향 (보수적)
|
||||
sell_threshold = 0.38 # 매도 기준도 상향
|
||||
print(f" ⚠️ [CAUTION Market] Buy threshold raised to 0.72")
|
||||
|
||||
# ===== 8. 매매 결정 =====
|
||||
decision = "HOLD"
|
||||
|
||||
# [신규] 강한 단일 신호 매수 로직 (기준 강화)
|
||||
strong_signal = False
|
||||
strong_reason = ""
|
||||
|
||||
if tech_score >= 0.80:
|
||||
strong_signal = True
|
||||
strong_reason = "Super Strong Technical"
|
||||
elif lstm_score >= 0.80 and ai_confidence >= 0.8:
|
||||
strong_signal = True
|
||||
strong_reason = f"High Confidence AI Buy (Conf: {ai_confidence})"
|
||||
elif sentiment_score >= 0.85:
|
||||
strong_signal = True
|
||||
strong_reason = "Strong News Sentiment"
|
||||
elif investor_score >= 0.1 and total_score >= 0.6: # 외인 수급이 좋고 전체 점수 양호
|
||||
strong_signal = True
|
||||
strong_reason = "Strong Foreigner Buying"
|
||||
|
||||
if strong_signal:
|
||||
decision = "BUY"
|
||||
print(f" 🎯 [{strong_reason}] Overriding to BUY!")
|
||||
elif total_score >= 0.60: # (0.5 -> 0.6 상향 조정으로 보수적 접근)
|
||||
decision = "BUY"
|
||||
elif total_score <= 0.30:
|
||||
decision = "SELL"
|
||||
|
||||
print(f" └─ Scores: Tech={tech_score:.2f} News={sentiment_score:.2f} LSTM={lstm_score:.2f} → Total={total_score:.2f} [{decision}]")
|
||||
|
||||
decision_reason = ""
|
||||
|
||||
# --- 보유 종목 분석 기반 매도 (신규) ---
|
||||
if holding_info:
|
||||
holding_yield = holding_info.get('yield', 0.0)
|
||||
holding_qty = holding_info.get('qty', 0)
|
||||
peak_price = holding_info.get('peak_price', current_price)
|
||||
|
||||
if holding_qty > 0:
|
||||
# A. 동적 손절 (ATR 기반)
|
||||
if holding_yield <= sl_tp['stop_loss_pct']:
|
||||
decision = "SELL"
|
||||
decision_reason = f"Dynamic Stop Loss ({holding_yield:.1f}% <= {sl_tp['stop_loss_pct']:.1f}%)"
|
||||
|
||||
# B. 동적 익절 (ATR 기반)
|
||||
elif holding_yield >= sl_tp['take_profit_pct']:
|
||||
decision = "SELL"
|
||||
decision_reason = f"Dynamic Take Profit ({holding_yield:.1f}% >= {sl_tp['take_profit_pct']:.1f}%)"
|
||||
|
||||
# C. 트레일링 스탑 (최고가 대비 하락)
|
||||
elif peak_price > 0:
|
||||
drop_from_peak = ((current_price - peak_price) / peak_price) * 100
|
||||
if drop_from_peak <= -sl_tp['trailing_stop_pct'] and holding_yield > 2.0:
|
||||
# 수익 상태에서만 트레일링 스탑 작동 (2% 이상 수익 확보)
|
||||
decision = "SELL"
|
||||
decision_reason = (f"Trailing Stop ({drop_from_peak:.1f}% from peak, "
|
||||
f"threshold: -{sl_tp['trailing_stop_pct']:.1f}%)")
|
||||
|
||||
# D. 분석 기반 매도 (점수가 매도 임계값 이하)
|
||||
if decision == "HOLD" and total_score <= sell_threshold:
|
||||
decision = "SELL"
|
||||
decision_reason = f"Analysis Signal (Score: {total_score:.2f} <= {sell_threshold:.2f})"
|
||||
|
||||
# E. 추세 반전 매도 (ADX 강한 하락추세)
|
||||
if decision == "HOLD" and adx_val >= 30:
|
||||
plus_di = ma_info.get('adx', 0) # 참고용
|
||||
mtf_align = ma_info.get('mtf_alignment', '')
|
||||
if mtf_align == 'STRONG_BEAR' and holding_yield < 0:
|
||||
decision = "SELL"
|
||||
decision_reason = f"Strong Bear Trend Reversal (MTF: {mtf_align})"
|
||||
|
||||
# --- 매수 판단 ---
|
||||
if decision == "HOLD":
|
||||
# 강한 단일 신호 매수 (기준 강화)
|
||||
strong_signal = False
|
||||
strong_reason = ""
|
||||
|
||||
# [강화] 복합 조건 매수 (단일 지표가 아닌 복합 조건)
|
||||
if tech_score >= 0.75 and lstm_score >= 0.6 and sentiment_score >= 0.6:
|
||||
strong_signal = True
|
||||
strong_reason = "Triple Confirmation (Tech+AI+News)"
|
||||
elif lstm_score >= 0.80 and ai_confidence >= 0.85 and adx_val >= 25:
|
||||
strong_signal = True
|
||||
strong_reason = f"High Confidence AI + Strong Trend (ADX={adx_val:.0f})"
|
||||
elif investor_score >= 0.10 and tech_score >= 0.60 and total_score >= 0.60:
|
||||
strong_signal = True
|
||||
strong_reason = "Institutional Buying + Good Fundamentals"
|
||||
elif ma_info.get('mtf_alignment') == 'STRONG_BULL' and tech_score >= 0.60:
|
||||
strong_signal = True
|
||||
strong_reason = f"Strong Multi-Timeframe Bullish + Tech {tech_score:.2f}"
|
||||
|
||||
if strong_signal and total_score >= buy_threshold - 0.05:
|
||||
# 강한 신호는 임계값 약간 완화 허용
|
||||
decision = "BUY"
|
||||
decision_reason = strong_reason
|
||||
print(f" 🎯 [{strong_reason}] → BUY!")
|
||||
elif total_score >= buy_threshold:
|
||||
decision = "BUY"
|
||||
decision_reason = f"Score {total_score:.2f} >= threshold {buy_threshold:.2f}"
|
||||
|
||||
# ===== 9. 포지션 사이징 =====
|
||||
suggested_qty = 0
|
||||
if decision == "BUY":
|
||||
# 기본 자산 1000만원으로 가정 (실제 run_cycle에서 덮어씀)
|
||||
suggested_qty = calculate_position_size(
|
||||
total_capital=10000000,
|
||||
current_price=current_price,
|
||||
volatility=volatility,
|
||||
score=total_score,
|
||||
ai_confidence=ai_confidence
|
||||
)
|
||||
if suggested_qty == 0:
|
||||
decision = "HOLD"
|
||||
decision_reason = "Position size too small"
|
||||
|
||||
print(f" └─ Scores: Tech={tech_score:.2f} News={sentiment_score:.2f} "
|
||||
f"LSTM={lstm_score:.2f} Inv={investor_score:.2f} → "
|
||||
f"Total={total_score:.2f} [{decision}]"
|
||||
f"{f' ({decision_reason})' if decision_reason else ''}")
|
||||
|
||||
return {
|
||||
"ticker": ticker,
|
||||
"score": total_score,
|
||||
"tech": tech_score,
|
||||
"sentiment": sentiment_score,
|
||||
"lstm_score": lstm_score,
|
||||
"investor_score": investor_score,
|
||||
"volatility": volatility,
|
||||
"volume_ratio": vol_ratio,
|
||||
"prediction": pred_result,
|
||||
"decision": decision,
|
||||
"decision_reason": decision_reason,
|
||||
"current_price": current_price,
|
||||
"ma_info": ma_info
|
||||
"ma_info": ma_info,
|
||||
"sl_tp": sl_tp,
|
||||
"suggested_qty": suggested_qty,
|
||||
"ai_confidence": ai_confidence,
|
||||
"ai_reason": ai_reason
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ [Worker Error] Failed to analyze {ticker}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
# 기본 실패 응답 반환 (프로세스 크래시 방지)
|
||||
return {
|
||||
"ticker": ticker,
|
||||
"score": 0.0,
|
||||
"decision": "HOLD",
|
||||
"decision_reason": f"Error: {str(e)}",
|
||||
"current_price": 0,
|
||||
"sl_tp": {'stop_loss_pct': -5.0, 'take_profit_pct': 8.0, 'trailing_stop_pct': 3.0},
|
||||
"suggested_qty": 0,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user