Files
QuantEngineByItz/tools/automate_routine.py
T
kjh2064 72f8d61244 feat: Sprint-3 완결 + Sprint-4 착수 (WBS-3.2, 3.4, 5.2)
주요 변경:
- [WBS-3.2] 리밸런싱 V2 신호 가중 목표배분 (signal_weighted_ss001_v1)
  * equal_weight -> SS001_Norm_Score 비례 버킷내 배분
  * 하네스: 삼성(36.84%) > SK하이닉스(29.16%), Core=66.00% PASS
- [WBS-3.4] logDailyAssetHistory_ SpreadsheetApp.getActiveSpreadsheet() -> getSpreadsheet_() 수정
  * run_all 컨텍스트에서 null 반환 방지
- [WBS-5.2] deploy_gas.py 전면 재작성
  * src/gas_adapter_parts/ + src/gas/ 양쪽 소스 탐색
  * gdc_01+gdc_02 -> gas_data_collect.gs 번들링
  * dry-run PASS: 17개 파일 WARN 0건
- src/gas/ 디렉토리 신규 추가 (CLASP 조직화 구조)
- tools/automate_routine.py, download_trading_data.py 신규 추가
- .gitignore: .clasprc.json OAuth 토큰 제외 추가
- ROADMAP_WBS.md: Sprint-3 [x] 완료, Sprint-4 착수 목록 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 16:22:19 +09:00

102 lines
3.8 KiB
Python

import json
import os
import requests
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
CLASPRC_PATH = ROOT / ".clasprc.json"
CLASP_PATH = ROOT / ".clasp.json"
SPREADSHEET_ID = "1e1TNlLfnT69nvw-I1wU_oBHmEtI2pfbld3e0fFmtrZM"
OUTPUT_XLSX = ROOT / "GatherTradingData.xlsx"
def get_tokens():
if not CLASPRC_PATH.exists():
raise FileNotFoundError(".clasprc.json not found")
with open(CLASPRC_PATH, "r", encoding="utf-8") as f:
return json.load(f)["tokens"]["default"]
def get_script_id():
if not CLASP_PATH.exists():
raise FileNotFoundError(".clasp.json not found")
with open(CLASP_PATH, "r", encoding="utf-8") as f:
return json.load(f)["scriptId"]
def refresh_access_token(tokens):
url = "https://oauth2.googleapis.com/token"
payload = {
"grant_type": "refresh_token",
"refresh_token": tokens["refresh_token"],
"client_id": tokens["client_id"],
"client_secret": tokens["client_secret"],
}
resp = requests.post(url, data=payload)
resp.raise_for_status()
return resp.json()["access_token"]
def run_gas_function(script_id, access_token, function_name):
url = f"https://script.googleapis.com/v1/scripts/{script_id}:run"
headers = {"Authorization": f"Bearer {access_token}"}
payload = {"function": function_name, "devMode": True}
print(f"Executing GAS function: {function_name} ...")
resp = requests.post(url, headers=headers, json=payload)
# Handle response
if resp.status_code != 200:
print(f"Error executing function: HTTP {resp.status_code}")
print(resp.text)
return False
result = resp.json()
if "error" in result:
print(f"Function execution failed: {json.dumps(result['error'], indent=2)}")
# Check if error is because it's not deployed as API Executable (even if user said it is, common issues persist)
return False
print("Function execution triggered successfully.")
return True
def download_spreadsheet(spreadsheet_id, access_token, output_path):
print(f"Downloading spreadsheet {spreadsheet_id} as XLSX...")
# Using Drive API v3 to export Google Sheet as XLSX
url = f"https://www.googleapis.com/drive/v3/files/{spreadsheet_id}/export?mimeType=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
headers = {"Authorization": f"Bearer {access_token}"}
resp = requests.get(url, headers=headers)
if resp.status_code == 403:
print("Error: 403 Forbidden. This usually means the OAuth token lacks the 'https://www.googleapis.com/auth/drive.readonly' or 'drive' scope.")
print("Please ensure your clasp login was done with proper scopes or manual token has Drive access.")
return False
resp.raise_for_status()
with open(output_path, "wb") as f:
f.write(resp.content)
print(f"Successfully downloaded to {output_path}")
return True
def main():
try:
tokens = get_tokens()
script_id = get_script_id()
access_token = refresh_access_token(tokens)
# Step 1: Execute GAS run_all
if run_gas_function(script_id, access_token, "run_all"):
print("Waiting a bit for GAS processes to finalize (optional)...")
time.sleep(5)
# Step 2: Download spreadsheet
if download_spreadsheet(SPREADSHEET_ID, access_token, OUTPUT_XLSX):
print("\nRoutine Part 1 & 2 complete.")
print("Final step: npm run prepare-upload-zip")
else:
print("\nDownload failed. Please check Drive API scopes.")
else:
print("\nGAS execution failed. Process aborted.")
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()