안녕하세요. 좋은 강의 잘 들었습니다. 막바지에 AWS 서버에 업로드 및 서버 실행을 하려고 할때, 기존 미국주식 자동매매도 강의를 듣고 돌리고 있던 사람은 어떤 형태로 업로드를 진행하면 되는지요? 신규로 올리는것과 동일한 방식으로 클로드에게 요청하면 되는지요? 그렇게 되면 두개의 프로그램간의 충돌이나, 리소스 문제는 없는지요? 다시한번 좋은 강의 감사드립니다. 감사합니다.
강의를 열심히 잘 따라하다가 supabase의 ticker_sentiment_analysis 테이블이 항상 비어있어서 클로드에게 물어보니 유료라서 못 가져 온다고 하네요. 알파밴티지의 정책이 바뀐것인지요? 아니면 뭔가 코드가 잘못된건지요? 대안은 있을지요? 아니요, 무료 플랜의 25회/일 한도 안에서는 사용할 수 없습니다. 한도(25회/일)가 병목이라는 말씀은 맞지만, NEWS_SENTIMENT 는 호출 횟수와 상관없이 처음부터 유료 전용입니다. 확인한 내용 1. 한도는 말씀하신 대로입니다. 공식 support 페이지와 premium 페이지 모두 무료는 "25 API requests per day"로만 적혀 있고, 분당 제한은 표기되지 않습니다. 하루 최대 25개 종목이면 호출 횟수 자체는 한도 안에 들어갑니다. 2. 문서에서 NEWS_SENTIMENT 는 유료 기능으로 표기돼 있습니다. 공식 API 문서의 해당 항목 제목이 Market News & Sentiment Trending Premium 이고, 예제 바로 아래에 이렇게 적혀 있습니다. 💡 Tip: this is a premium API function. Subscribe to a premium membership plan to instantly unlock all premium APIs. support 페이지의 무료 설명은 "the majority of our datasets"로, 전부 무료라는 뜻이 아닙니다. 3. 실제 호출 결과도 같습니다. 오늘 이 키로 호출했습니다. 호출결과 NEWS_SENTIMENT ( tickers=AAPL 만, 문서 예제와 동일) premium endpoint 거부 OVERVIEW premium endpoint 거부 (문서에서도 Premium 표기) TIME_SERIES_DAILY (무료로 표기된 엔드포인트) 정상 (일봉 100개) NEWS_SENTIMENT 는 limit 이나 time_from 같은 파라미터와 무관하게, 가장 단순한 호출에서도 거부됩니다. 같은 키로 무료 엔드포인트는 정상 동작하므로 키나 일일 한도의 문제도 아닙니다. 정리하면 한도 문제가 아니라 엔드포인트 접근권한 문제입니다. 25회 안에서 쓰는 것과는 별개입니다. 지금 코드의 호출 방식에는 문제가 없습니다. 무료 키에서는 아무리 호출을 줄여도 데이터를 받을 수 없습니다. 이 시스템이 쓰는 NEWS_SENTIMENT 가 유료가 된 시점은 이 확인만으로는 알 수 없습니다. 오늘 기준으로 유료라는 것만 확인했습니다. 문서에서 같이 Premium으로 표기된 기능이 더 있습니다. TIME_SERIES_DAILY_ADJUSTED , 주요 지수(S&P 500, VIX 등), Company Overview 입니다. stock.py 등에서 이 중 하나를 쓰고 있다면 같은 이유로 조용히 실패하고 있을 수 있어서, 관련 호출을 점검해 보는 것을 권장합니다. 가능한 대안 유료 플랜: 가장 싼 플랜이 월 $49.99(분당 75회)입니다. 무료로 가능한 감성 분석 대체: 뉴스 헤드라인을 다른 무료 소스에서 가져와 Claude로 점수를 매기는 방법입니다. 구현하면 ticker_sentiment_analysis 를 다시 채울 수 있습니다. 참고: 문서에는 "verified open-source or educational projects는 무제한"이라는 문구가 있습니다. 이 프로젝트가 학생 프로젝트로 인정될 수 있는지 Alpha Vantage 지원 1 에 문의해 볼 수도 있습니다. 다만 이 문구가 유료 엔드포인트 접근까지 열어 주는지는 제가 확인하지 못했습니다. 대체 구현이 필요하면 말씀해 주세요. 어떤 뉴스 소스를 쓸지(yfinance, Finnhub 등)부터 같이 정해야 합니다.
안녕하세요 몇가지 질문이 있습니다 2주동안 컴퓨터를 못해서 오랜만에 들어갔더니 colab를 실행해 봤는데 사진처럼 떠서요 어떻게 해야될까요 ? 터미널 python run.py 가 잘돌아가면 상관없을까요? 클라우드에서 작동 안시키고 그냥 컴퓨터를 작동시킬려고 하는데 터미널의 python run.py 만 실행시키면 될까요?
저장은 되게 되었습니다. 근데 이게 ai가 db에 그냥 아무 데이터나 넣게 학습 시키게끔 바꾼건지 잘 모르겠습니다. # 예전: 종목 목록을 만든 뒤 바로 스케일링했다. # 지금: 숫자가 하나도 없는 칸은 학습에서 뺀다. def columns_with_numbers(frame, columns): kept = [] dropped = [] for col in columns: numeric = pd.to _numeric(frame[col], errors='coerce') if col in frame.columns else pd.Series(dtype=float) if numeric.notna().any() and np.isfinite( numeric.to _numpy(dtype=float, na_value=np.nan)).any(): kept.append(col) else: dropped.append(col) return kept, dropped target_columns, dropped_targets = columns_with_numbers(data, target_columns) economic_features, dropped_econ = columns_with_numbers(data, economic_features) dropped_columns = dropped_targets + dropped_econ if dropped_columns: print(f"값이 없어 학습에서 뺀 칸: {dropped_columns}") if not target_columns or not economic_features: raise RuntimeError("학습에 쓸 숫자 칸이 없습니다.") # 예전: 빈 칸을 그대로 스케일러에 넣었다. # 지금: 남은 빈 칸은 앞뒤 날짜 값으로 채운 다음 스케일링한다. feature_columns = list(dict.fromkeys(target_columns + economic_features)) data[feature_columns] = data[feature_columns].apply( pd.to _numeric, errors='coerce').ffill().bfill() print("Scaling data...") train_size = int(len(data) * 0.8) train_data = data.iloc[:train_size] test_data = data.iloc[train_size:] data_scaled = data.copy() stock_scaler = MinMaxScaler() econ_scaler = MinMaxScaler() data_scaled[target_columns] = stock_ scaler.fit _transform(data[target_columns]) data_scaled[economic_features] = econ_ scaler.fit _transform(data[economic_features]) model = build_transformer_with_two_inputs(stock_shape, econ_shape, num_heads=8, ff_dim=256, target_size=len(target_columns)) # 예전: Adam(learning_rate=0.0001) # 지금: clipnorm=1.0 을 넣어 학습 중 숫자가 한없이 커지지 않게 했다. model.compile(optimizer=Adam(learning_rate=0.0001, clipnorm=1.0), loss='mse', metrics=['mae']) predicted_prices = model.predict([X_stock_full, X_econ_full], verbose=1) predicted_prices_actual = stock_scaler.inverse_transform(predicted_prices) # 예전: 예측하자마자 표로 만들고 저장했다. # 지금: 빈 칸 개수를 세고, 가격으로 되돌린 값이 전부 비어 있으면 저장하지 않고 멈춘다. raw_empty = int((~np.isfinite(predicted_prices)).sum()) inv_empty = int((~np.isfinite(predicted_prices_actual)).sum()) last_loss = history.history['loss'][-1] print(f"모델 출력 빈 값: {raw_empty} / {predicted_prices.size}") print(f"가격으로 되돌린 빈 값: {inv_empty} / {predicted_prices_actual.size}") print(f"마지막 학습 손실: {last_loss}") if inv_empty == predicted_prices_actual.size: raise RuntimeError( "모델 예측이 전부 비어 있습니다. " f"모델 출력 빈 값 {raw_empty}/{predicted_prices.size}, " f"가격 변환 빈 값 {inv_empty}/{predicted_prices_actual.size}, " f"마지막 학습 손실 {last_loss}. 데이터베이스는 수정하지 않았습니다." ) def save_predictions_to_db(result_df): try: # 예전: records = result_ df.to _dict('records') # 지금: 숫자는 그대로 두고, 비어 있는 칸만 null 로 바꾼다. records = json.loads(result_ df.to _json(orient='records')) if not records: raise ValueError("저장할 예측 결과가 없습니다.") # 예전: 넣을 내용을 확인하기 전에 기존 행을 지웠다. # 지금: 넣을 내용이 생긴 뒤에만 기존 행을 지운다. supabase.table("predicted_stocks").delete().neq("id", 0).execute() chunk_size = 100 for i in range(0, len(records), chunk_size): chunk = records[i:i+chunk_size] response = supabase.table("predicted_stocks").insert(chunk).execute() # 예전: 저장 응답이 비어 있어도 넘어갔다. # 지금: 응답이 비어 있으면 실패로 처리한다. if not response.data : raise RuntimeError(f"예측 결과 저장 응답이 비어 있습니다. offset={i}") print(f"{len(records)}개의 예측 결과가 데이터베이스에 저장되었습니다.") # 예전: 성공인지 실패인지 돌려주지 않았다. # 지금: 성공이면 True 와 빈 이유를 돌려준다. return True, None except Exception as e: print(f"데이터베이스 저장 오류: {e}") # 지금: 실패하면 False 와 실패 이유를 돌려준다. return False, str(e) # 예전: save_predictions_to_db(result_data) # print("모든 예측 결과가 DB에 저장되었습니다.") # 지금: 저장이 실패하면 성공이라고 말하지 않고, 이유를 보여 준 뒤 멈춘다. predictions_saved, save_error = save_predictions_to_db(result_data) if not predictions_saved: raise RuntimeError(f"예측 결과를 데이터베이스에 저장하지 못했습니다. 원인: {save_error}") print("모든 예측 결과가 DB에 저장되었습니다.") def get_predictions_from_db(chunk_size=1000): try: count_response = supabase.table("predicted_stocks").select("id", count="exact").execute() # 예전: total_count = count_response.count # 지금: 개수가 없으면 0으로 본다. total_count = count_response.count or 0 print(f"predicted_stocks 테이블의 총 레코드 수: {total_count}") # 예전: 행이 0개여도 날짜 칸을 읽다가 오류가 났다. # 지금: 행이 0개면 날짜를 읽지 않고 None 을 돌려준다. if total_count == 0: print("predicted_stocks 테이블에 데이터가 없습니다.") return None all_data = [] for offset in range(0, total_count, chunk_size): response = ( supabase.table("predicted_stocks") .select("*") .order("날짜", desc=False) .limit(chunk_size) .offset(offset) .execute() ) # 예전: chunk_data = response.data # 지금: 응답이 비어 있으면 빈 목록으로 본다. chunk_data = response.data or [] all_data.extend(chunk_data) df = pd.DataFrame(all_data) # 예전: 날짜 칸이 없어도 df['날짜'] 를 읽었다. # 지금: 표가 비었거나 날짜 칸이 없으면 None 을 돌려준다. if df.empty or '날짜' not in df.columns: print("가져온 데이터에 '날짜' 컬럼이 없습니다.") return None df['날짜'] = pd.to _datetime(df['날짜']) return df except Exception as e: print(f"데이터 가져오기 오류: {e}") return None # 예전: # data = get_predictions_from_db(chunk_size=1000) # if data is None or len(data) == 0: # print("데이터를 가져오는데 실패했습니다.") # exit(1) # target_columns = ['애플', '마이크로소프트', ...] # 지금: 데이터베이스를 다시 읽지 않고, 방금 만든 예측표를 그대로 평가한다. # 종목 목록도 다시 적지 않고, 위에서 걸러 둔 target_columns 를 쓴다. data = result_data.copy() data['날짜'] = pd.to _datetime(data['날짜']) forecast_horizon = 14 evaluation_results = evaluate_predictions(data, target_columns, forecast_horizon) rise_results = analyze_rise_predictions(data, target_columns) # 예전: 비교할 예측이 없어도 바로 두 표를 합쳤다. # 지금: 평가 결과가 비어 있으면 합치지 않고 멈춘다. if evaluation_results.empty or 'Stock' not in evaluation_results.columns: raise RuntimeError("평가할 예측값이 없습니다. 실제 가격과 예측값을 합치지 않습니다.") final_results = pd.merge(evaluation_results, rise_results, on='Stock', how='outer') 이런식으로 바꾸었어요 AI가
강의를 수강하시다가 막히는 부분이 있는 경우는 "자주 묻는 질문"을 먼저 참고 부탁드립니다. 막히는 부분에 대해서 질문을 주실 때에는 꼭 강의 제목과 타임라인도 같이 작성 부탁드립니다. 현재, AWS lightsail 통해 자동화하는 부분 강의 보다가, supabase table에 signal_snapshots 가 없는 것을 확인하였습니다. 아마 10. 멀티모달 이후 여러가지가 한번에 추가되면서 일부 누락된것 같은데, 해당 부분 이후 클라우드 배포 문서 제외 모든 문서 (1~17)과 프로젝트 내 코드 비교를 실행하니 누락되거나 없다고 하는 부분이 다수 있더라구요. 혹시 코드와 문서 대비 어디까지 반영이 되어 있는 것인지 구분이 한번 가능하실까요?
모든 예측 결과가 DB에 저장되었습니다. predicted_stocks 테이블의 총 레코드 수: 0 총 0개 데이터를 성공적으로 가져왔습니다! 데이터 가져오기 오류: '날짜' 데이터를 가져오는데 실패했습니다. --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) /tmp/ipykernel_921/1990775403.py in <cell line: 0>() 520 521 # 3) Evaluate predictions --> 522 evaluation_results = evaluate_predictions(data, target_columns, forecast_horizon) 523 print("============ Evaluation Results ============") 524 print(evaluation_results) /tmp/ipykernel_921/1990775403.py in evaluate_predictions(data, target_columns, forecast_horizon) 382 383 # Check if the columns exist --> 384 if predicted_col not in data.columns or actual_col not in data.columns: 385 print(f"Skipping {col}: Columns not found in data ({predicted_col}, {actual_col})") 386 continue AttributeError: 'NoneType' object has no attribute 'columns' supabase api 키 입력해서 경제지표랑 주가를 supase에 자동으로 저장하는것까지 됐는데. ai 예측모델 하니까 저런식으로 나오네요..
섹션 5를 듣기 전에 섹션6 슬랙 연동을 먼저 했습니다. 질문이 생겼습니다. 첫번째 질문. 그 과정에서 한 가지 안 사실은 이미 수업시간에 말씀하신 것들은 수정이 다 되어있었다는 것입니다. 새로 받은 개인 수치들만 넣어야 한다는 점 외는 고칠 것이 없더라구요. 혹시나 고쳐야 되나, 수업 영상과 받은 화일을 비교하느라 고생했습니다. ^^; 그래서 말인데요. 슬랙 연결보다 케겔 연동이 보기에 어렵고 긴 작업 같은데. 이것도 새 계정 만들어서 키랑 시크릿 키만 넣으면 되는 것인지 확인하고 싶습니다. 두번째 질문. 그리고... 이건 예전에 해결해야 했나 싶은데, host:8000 <- 주소를 수업영상에서 봐서 정확하게 모릅니다만. 찍으면 화면이 나와야 하는데, 글자만 나오고 더 안 나와서. 이건 어디를 참조해야 하는지 설명 부탁드립니다. 감사합니다.
10강 들었습니다. 미국 써머타임을 적용하는 기간과 아닌 기간의 시작 시간이 달라질 텐데. 그건 수작업으로 바꿔야 하나요? 아니면 GMT? UMT? 시간 기준으로 자동으로 바뀌게 되나요? 궁금합니다. 강의에도 확정되지 않는 데이터는 넣지 않는다고 하는데. 늦게 시작하고, 늦게 끝나게 되면. 꼬일 것 같아서요.
클로드 API를 만들어서 복붙하면 될 것 같은데요. 강의 영상과 API 만드는 과정이 약간 달라지긴 했어도 크게 문제없이 진행했는데요. 마지막에 가서 기간 설정이 나왔는데요. 기한 없음도 있는데. 이걸 선택하니 경고문구가 당장 뜨는데, 1달 짜리 쓰면 언제 그랬냐는 듯이 멈출 것 같아서요. 어떤 선택을 해야할 지 알려주시겠어요?
강의를 수강하시다가 막히는 부분이 있는 경우는 "자주 묻는 질문"을 먼저 참고 부탁드립니다. 막히는 부분에 대해서 질문을 주실 때에는 꼭 강의 제목과 타임라인도 같이 작성 부탁드립니다. 베개 투자법: 자면서 돈 버는 AI 주식 자동 매매 머신 python fastapi 부분을 듣고 있습니다. 화면 아래를 보면, 기본 강의 자료와 강의 자료 2개가 항상 올라와있는데, 이 두 개는 같은 것이 강의 내내 올라오는 것인지가 궁금하고, 이 두 개를 어떻게 나눠서 사용해야 할 지 궁금합니다. 그리고, 각 해당 항목에 들어가면 예제 코드가 있나요? 나중에는 하나로 묶인 코드도 제공되는거지요?
현재 강의 목차와 실제 영상 내용이 일치하지 않습니다. 예를 들어, [11. 무엇을 살 것인가 - 25종목 유니버스 설계]를 재생하면 프로젝트 초기 세팅 내용이 나오고 [14. 6000일·96개 지표를 담는 무결성 데이터 설계]를 재생하면 매매 대상 25종목 유니버스 선정 내용이 나옵니다. 이외에도 일치하지 않는 영상이 많습니다. 이에 따라 섹션 구분도 잘못되어 있어 학습을 진행하기가 어렵습니다. 학습에 지장이 없도록 전체적인 영상 순서와 섹션 구분을 빠르게 확인하고 강의 목차에 맞는 영상으로 수정 업로드해 주시기를 요청드립니다.
오랜만에 수익률을 보러 테스트 모의투자 환경에서 실행을 로그인하니, 상시투자가 튕겨져 있더군요(상시투자 재신청을 했어야 했습니다) 근데 슬랙 알림을 보니, 계속 투자 판단이 이뤄진 것으로 보여 가만히 놔뒀습니다(계속 서버 비용과 AWS lightsail 비용이나가는 상황) 이 경우에 대해서 혹시 제가 모르는 부분이 있었는지 설명해주실 수 있을까요? 모의투자 히스토리도 다 날라갔네요.
■ 1. 통합 파이프라인 → POST /pipeline/run-buy-pipeline 을 실행 시 아래와 같이 LLM 호출 전체 실패가 뜹니다. { "message": "통합 파이프라인 완료 - BUY 0개 / HOLD 1개", "steps": { "1_technical_analysis": { "message": "25개의 추천 데이터가 생성되었습니다", "count": 25, "elapsed_sec": 60.9 }, "1.5_earnings_calendar": { "message": "저장할 실적 일정이 없습니다", "count": 0 }, "2_sentiment_analysis": { "message": "10개의 티커(추천 주식: 10개, 보유 주식: 0개)를 분석했습니다", "count": 10, "elapsed_sec": 97.8 }, "3_candidate_extraction": { "message": "1개의 매수 추천 주식을 찾았습니다 (v2 (z-score))", "count": 1, "elapsed_sec": 1 }, "4_llm_review": { "buy_count": 0, "hold_count": 1, "elapsed_sec": 51.3 } }, "candidates_before_llm": 1, "candidates_after_llm": 0, "held": 1, "results": [], "held_results": [ { "ticker": "ADBE", "stock_name": "어도비", "accuracy": 87.6874, "rise_probability": 10.1176571624871, "last_price": 266.510009765625, "predicted_price": 293.474578857422, "recommendation": "STRONG BUY", "analysis": "어도비 is expected to rise by about 10.12%. Consider buying or holding.", "sentiment_score": 0.0197778333333333, "article_count": 6, "sentiment_date": "2026-09-09 00:11:55", "technical_date": "2026-09-06", "sma20": 277.896499633789, "sma50": 260.969197692871, "golden_cross": true, "rsi": 49.811422658345, "macd": 3.41914308274363, "signal": 6.66046215382312, "macd_buy_signal": false, "technical_recommended": false, "volume_ratio": 1.6, "adx": 24.58, "earnings_date": null, "earnings_estimate": null, "days_to_earnings": null, "composite_score": 0.6117, "v2_factors": { "z_rise": 1.29, "z_tech": -0.231, "z_macd": -1.375, "z_sma": 1.408, "z_rsi": -0.727, "z_sent": -1.365, "z_vol": 1.401, "z_adx": 1.397 }, "vix_value": 14.5299997329712, "scoring_version": "v2" } ], "llm_reasoning": "LLM 호출 전체 실패 (Opus 3회 + Sonnet 3회): Messages.create() got an unexpected keyword argument 'temperature'", "total_elapsed_sec": 211 } ■ 1.1 그리고 이 부분을 클로드에게 물어보니 아래와 같은 답변을 받았습니다. app/services/llm_review_service.py:181 의 create_kwargs["temperature"] = 0 입니다. 이 프로젝트에 설치된 SDK는 anthropic 1.2.0 인데, 1.x 메이저 업그레이드에서 temperature / top_p / top_k 가 Messages.create() 시그니처에서 완전히 삭제됐습니다. 실제 설치본 확인 결과: max_tokens, messages, model, cache_control, container, inference_geo, metadata, output_config, service_tier, stop_sequences, stream, system, thinking, tool_choice, tools, user_profile_id, extra_headers, extra_query, extra_body, timeout temperature 가 없습니다. 그래서 API 요청이 나가기도 전에 파이썬 레벨에서 TypeError: Messages.create() got an unexpected keyword argument 'temperature' 가 터집니다. 서버 400 에러가 아니라 SDK 호출 자체가 실패한 것이라, 재시도해도 100% 같은 결과입니다. 코드의 전제가 어긋난 부분: # temperature 파라미터를 받지 않는 모델 (Opus 4.7부터 sampling 파라미터 제거됨) MODELS_WITHOUT_TEMPERATURE = {"claude-opus-4-8"} "모델별로 다르다"는 전제로 Opus만 제외했지만, SDK 1.x에서는 모델과 무관하게 어떤 모델에도 temperature를 넘길 수 없습니다. 그래서 폴백 모델 claude-sonnet-4-6 차례에서 매번 터졌습니다. Opus 3회는 왜 실패했나 (추정) 최종 메시지의 에러는 last_error 한 개만 찍히는데, Sonnet의 TypeError가 Opus 쪽 에러를 덮어썼습니다. 다만 4_llm_review 의 elapsed_sec: 51.3 이 단서입니다 — 재시도 대기는 5+15+30 = 50초이고, 이건 한 모델만 3회 재시도(sleep 포함)를 돌았다는 뜻입니다. 즉 Opus는 sleep 없이 break 되는 경로 — json.JSONDecodeError(191행) 또는 529/429/503 이외의 APIStatusError(246행) — 로 1회만에 빠져나갔고, 50초는 Sonnet의 TypeError 3회입니다. Opus의 실제 에러는 콘솔 stdout(LLM API 에러 ... 또는 LLM 응답 JSON 파싱 실패 ...)에만 남아 있으니 그 줄을 확인해야 확정됩니다. 수정 방향 1. temperature 분기 전체 삭제 (MODELS_WITHOUT_TEMPERATURE 상수 포함). 결정성이 필요하면 프롬프트/output_config로 제어합니다. 2. TypeError 같은 비재시도성 예외는 50초 sleep 후 재시도하지stdout(LLM API 에러 ... 또는 LLM 응답 JSON 파싱 실패 ...)에만 남아 있으니 그 줄을 확인해야 확정됩니다. 수정 방향 1. temperature 분기 전체 삭제 (MODELS_WITHOUT_TEMPERATURE 상수 포함). 결정성이 필요하면 프롬프트/output_config로 제어합니다. 2. TypeError 같은 비재시도성 예외는 50초 sleep 후 재시도하지 말고 즉시 다음 모델/중단으로 보내기. 3. last_error 를 모델별로 모아서 실패 사유에 함께 남기기 (지금은 Opus 원인이 통째로 유실됩니다). 4. 모델 ID도 현행 세대(claude-opus-5 / 폴백 claude-sonnet-5)로 올리는 걸 권합니다. 수정 적용해 드릴까요? 1·2·3번은 바로 반영 가능하고, 4번은 원하시면 같이 하겠습니다. ■ 2. llm_decision_logs 미적재 문의 (캐글 연동은 되었음) (a) /pipeline/kaggle/trigger-ml 은 원래 이 테이블을 안 건드립니다 pipeline.py:298 의 kaggle_trigger_ml() 은 kaggle kernels push → status 폴링만 합니다. review_buy_candidates() 를 호출하는 경로가 아예 없어요. llm_decision_logs 에 쓰는 건 /pipeline/run-buy-pipeline 과 스케줄러의 일일 파이프라인뿐입니다. trigger-ml 을 아무리 돌려도 이 테이블은 안 채워집니다. (b) run-buy-pipeline 을 돌려도 저장이 실패하고 있습니다 — 컬럼 누락 Supabase에 직접 조회해서 확인했습니다(읽기만 했고 쓰기는 안 했습니다): ┌──────────────────────────────────────────┬────────────┐ │ 컬럼 │ 상태 │ ├──────────────────────────────────────────┼────────────┤ │ decision_date, ticker, stock_name, │ │ │ decision, reason, market_analysis, │ OK │ │ composite_score, rise_probability, rsi, │ │ │ adx, vix_value, created_at, updated_at │ │ ├──────────────────────────────────────────┼────────────┤ │ │ ❌ 42703 │ │ earnings_date │ column │ │ │ does not │ │ │ exist │ ├──────────────────────────────────────────┼────────────┤ │ days_to_earnings │ ❌ 42703 │ ├──────────────────────────────────────────┼────────────┤ │ earnings_estimate │ ❌ 42703 │ └──────────────────────────────────────────┴────────────┘ llm_review_service.py:24 의 upsert 페이로드에는 이 세 컬럼이 들어갑니다. 컬럼이 없으니 매번 요청이 거부되고, save llm_decision_logs 는 전체를 try/except Exception 으로 감싸 LLM 판단 로그 저장 실패: 만 출력하고 넘어갑니다(llm_review_service.py:41). 그래서 API 응답은 정상으로 보이는데 테이블만 계속 비어 있는 겁니다. 현재 행 수는 0개입니다. 같은 원인으로 earnings_calendar 테이블 자체도 없습니다(PGRST205). 어제 실행 결과의 1.5_earnings_calendar: "저장할 실적 일정이 없습니다" 도 이것 때문입니다. 원인은 sql/create_earnings_calendar.sql 을 Supabase에서 실행하지 않은 것입니다. 파일은 리포에 있는데 DB에 적용이 안 됐어요. Supabase SQL Editor에서 그 파일을 통째로 실행하면 테이블 생성 + 컬럼 3개 추가가 한 번에 됩니다. 한 가지 더: .env 의 SUPABASE_KEY 는 anon 역할입니다. 조회는 되는 걸 확인했지만 INSERT는 RLS에 막힐 수 있으니, 컬럼 추가 후에도 안 들어가면 sql/errorfix.sql 의 ALTER TABLE llm_decision_logs DISABLE ROW LEVEL SECURITY 를 확인하세요. --- 정리하면 이 순서로 풀립니다: 1. Supabase에서 sql/create_earnings_calendar.sql 실행 → 컬럼/테이블 생성 (이건 직접 하셔야 합니다) 2. llm_review_ service.py 의 temperature 제거 → LLM 호출 자체가 성공해야 BUY/HOLD 판정이 저장됨 3. /pipeline/run-buy-pipeline 실행 (trigger-ml 아님) 2번과, 로그 저장 실패가 조용히 묻히지 않게 API 응답에 노출시키는 수정을 지금 적용할까요?
현재 섹션 7까지는 전부 들었는데 따로 모의투자 매매도 안되고 llm쪽 슈퍼베이스 데이터도 안들어가서 문의드려요. 클로드는 아래처럼 말해주는데 이해가 잘 안돼서요ㅠㅠ env에는 아래값들 전부 넣어줬습니다. KAGGLE_USERNAME= KAGGLE_API_TOKEN= KAGGLE_KERNEL_SLUG= KAGGLE_NOTEBOOK_DIR= SLACK_WEBHOOK_URL= SLACK_NOTIFY_LEVEL=info USE_SCORING_V2=true 아래는 제가 클로드에게 모의투자 계좌 연동했는데 왜 안사는지 질문한 내용입니다. 결론: 계좌 연동 문제 아닙니다. LLM 최종 검토에서 전부 거부당하고 있습니다. stock_scheduler.log를 보면 시스템은 오늘 끝까지 정상으로 돌았습니다. 21:00:00 Daily Pipeline 시작 21:00:04 [2/4] Kaggle 예측 push → 21:08:14 완료 (482초) 21:10:32 [4/4] LLM 최종 검토 + KIS 매수 주문 시작 21:10:37 현재 보유/주문 중인 종목 수: 0 21:10:38 매수 후보 6개 → LLM 최종 검토 시작 21:11:31 LLM 검토 결과 매수 대상이 없습니다. ← 여기서 끝 어제도 똑같았습니다: 후보 7개 → LLM 전원 HOLD. 이틀 연속 매수 0건이고, trade_records 테이블도 0행입니다. 즉 데이터 수집 → ML 예측 → 기술지표 → 점수 산출까지 다 통과해서 후보 6개가 올라왔는데, 마지막 LLM 관문(llm_review_ service.py )이 6개를 전부 HOLD 처리했습니다. LLM은 거부권만 있어서(코드 주석 49번 줄) 여기서 막히면 주문이 아예 안 나갑니다. "시간이 지나야 하냐" — 반은 맞습니다 매수 경로가 두 개입니다. ┌─────────────────────────┬────────────────────────────────┬───────────────────┐ │ 경로 │ 실행 시각 │ 오늘 결과 │ ├─────────────────────────┼────────────────────────────────┼───────────────────┤ │ 일일 파이프라인 │ 매일 KST 21:00 │ 실행됨 → LLM 전원 │ │ (force=True) │ │ HOLD │ ├─────────────────────────┼────────────────────────────────┼───────────────────┤ │ 스케줄러 자동매수 │ 평일 10:30~10:35 ET = KST │ 아직 안 옴 (28분 │ │ │ 23:30~23:35 │ 뒤) │ └─────────────────────────┴────────────────────────────────┴───────────────────┘ 지금 23:01 KST(10:01 ET)라 두 번째 시도가 곧 있습니다. 오늘 매수 성공이 0건이라 last buy_date가 갱신되지 않았으므로(scheduler.py:811-815) 23:30에 정상적으로 재시도됩니다. 다만 그 시도도 똑같은 LLM 관문을 통과해야 합니다. 지금 상태면 또 막힐 가능성이 큽니다. LLM이 왜 다 거부하나 — 프롬프트가 종목을 실제보다 나쁘게 보여주고 있습니다 코드를 보니 LLM에게 넘어가는 데이터에 문제가 두 개 있습니다. 1. v2 모드인데 프롬프트는 v1 키를 읽습니다 (제일 큼) llm_review_service.py:110: (예측상승률: {c.get('rise_score', 0)}, 기술: {c.get('tech_score', 0)}, 거래량: {c.get('volume_score', 0)}, ADX: {c.get('adx_score', 0)}, VIX: {c.get('vix_score', 0)}) 이 다섯 키는 compute_v1()만 만듭니다. USE_SCORING_V2=true인 지금은 compute_v2()가 v2_factors만 넣기 때문에 전부 없는 키 → 기본값 0으로 출력됩니다. LLM은 매 종목마다 이렇게 봅니다: - 종합점수: 0.8532 (예측상승률: 0, 기술: 0, 거래량: 0, ADX: 0, VIX: 0) 종합점수는 양수인데 근거 점수가 전부 0인 모순된 데이터입니다. "데이터가 이상하면 HOLD" 성향의 프롬프트(138번 줄 "시스템 오류 가능성 → HOLD")와 만나면 전멸이 자연스럽습니다. 2. RSI 라벨이 틀렸습니다 llm_review_ service.py :103은 RSI가 50~65일 때만 "(강세 진입)"이라 쓰고, 30~50이면 "(매수구간 아님)"으로 표기합니다. 그런데 사전필터(scoring_service.py:42)는 RSI ≤ 65를 전부 매수 구간으로 봅니다. 지금 DB의 25개 종목 중 16개가 RSI 30~50 구간이라, 시스템이 통과시킨 종목을 프롬프트가 "매수구간 아님"이라고 LLM에게 일러바치는 셈입니다. 여기에 ADX가 20 미만인 종목이 13/25라 "(추세 약함)"까지 붙으면, LLM 입장에서 HOLD를 안 낼 이유가 없습니다. 근거를 DB에서 못 보는 이유도 찾았습니다 llm_decision_logs 테이블이 0행입니다. 판단 이유가 하나도 안 남아 있어요. 원인은 저장 코드가 실제 테이블에 없는 컬럼 3개를 쓰기 때문입니다: column llm_decision_logs.earnings_date does not exist (42703) column llm_decision_logs.days_to_earnings does not exist column llm_decision_logs.earnings_estimate does not exist llm_review_service.py:36-38이 실적 정보를 upsert에 넣는데, create_all_tables.sql:203-219의 테이블 정의에는 그 컬럼들이 없습니다. 매번 예외가 나고 except가 삼켜서(42-43번 줄) 조용히 넘어갑니다. 지금 바로 확인할 곳 .env에 Slack 웹훅이 설정돼 있으니, 오늘 21:11에 온 Slack 메시지를 보시면 LLM이 6개 종목을 각각 왜 HOLD했는지 이유가 그대로 적혀 있습니다. - "오늘의 LLM 판단" 류 메시지가 왔다면 → LLM이 정상 응답했고 실제로 전부 거부한 것 - "LLM 장애" 알림이 왔다면 → API 호출 자체가 실패해서 Fail-Close로 막힌 것 (llm_review_service.py:264-275) python run.py 를 띄운 터미널 창에도 LLM HOLD: 종목명(티커) - 이유 가 줄줄이 찍혀 있습니다. 이건 print라서 로그 파일에는 안 들어갑니다. --- 원인 확인만 요청하셨던 흐름이라 코드는 아직 손대지 않았습니다. 위 세 가지(프롬프트 v2 대응, RSI 라벨, llm_decision_logs 컬럼)를 고치면 LLM이 제대로 된 데이터를 보고 판단하게 됩니다. 고쳐드릴까요?