Compare commits

...

3 Commits

Author SHA1 Message Date
kyu f87756ee2e ci: add minimal Jenkinsfile (validate-only, no deploy)
trading/pipeline/head This commit looks good
Stages: checkout info, repo layout, compose syntax validation.
No build/deploy yet — used to verify Jenkins ↔ Gitea wiring before
adding real deployment stages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:08:10 +09:00
kyu da36454e21 score-engine: 백필 모드로 가중치 학습 표본 87 → 15,287 (175배)
calculate_daily_scores가 today 하드코딩이라 과거 시점 score 재계산 불가, 결과적으로
가중치 학습이 18일치 빈약 표본(87건)으로만 돌아 10공식 중 7개 가중치=0,
백테스트 평균 -5.59%·승률 12.6%로 시스템이 거짓 패턴 학습 중이었음.

as_of: date 매개변수로 백필 모드 도입, look-ahead bias 차단:
- 헬퍼 7개(_load_insider/consensus/macro/inst_flow, calc_market_regime,
  calc_trend_score, calc_momentum/amihud/beta) 시점 매개변수화
- weight_config는 config_date <= as_of 필터
- reliability/credibility 캐시는 백필 모드 비활성화
- DART 재무는 보고서 종류별 표준 공시일 추정으로 시점 필터
- analyst_consensus는 이력 미보존이라 백필 모드 제외

POST /score/backfill, GET /score/backfill/status 엔드포인트 추가.
96일치 백필 결과: GP/A(Novy-Marx 2013)가 진짜 알파 공식으로 학습됨,
강력매수 등급 평균 +2.96%·승률 66.7%·시장 알파 -0.31%로 거의 시장 따라잡음.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:48:39 +09:00
kyu 3db047bade news-collector: process_raw 4-동시 병렬화로 처리 용량 2배
배치 200건이 직렬 처리로 30분 초과해 매시 1회로 후퇴, 일 4,800건 처리에 그쳐
일 신규 5,500건 유입을 못 따라잡고 백로그 16K가 정체 상태.
asyncio.Semaphore(4)로 병렬화(OLLAMA_NUM_PARALLEL=4와 매칭) → 한 배치 6분 20초,
스케줄러를 minute="*/30"으로 복원 (max_instances=1로 중첩 방지).
일 처리 용량 9,600건 → 백로그 자연 소화.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 00:48:39 +09:00
3 changed files with 1068 additions and 289 deletions
Vendored
+57
View File
@@ -0,0 +1,57 @@
pipeline {
agent any
options {
timestamps()
buildDiscarder(logRotator(numToKeepStr: '20'))
timeout(time: 10, unit: 'MINUTES')
}
stages {
stage('Checkout info') {
steps {
script {
echo "Branch: ${env.BRANCH_NAME ?: 'n/a'}"
echo "Commit: ${env.GIT_COMMIT ?: 'n/a'}"
echo "Build : #${env.BUILD_NUMBER} on ${env.NODE_NAME}"
}
sh 'git log -1 --pretty=fuller || true'
}
}
stage('Repo layout') {
steps {
sh '''
echo "=== top-level ==="
ls -la | head -30
echo
echo "=== compose files ==="
ls -la docker-compose*.yml 2>/dev/null || true
'''
}
}
stage('Validate compose') {
steps {
sh '''
if command -v docker >/dev/null 2>&1; then
echo "docker version:"
docker --version
echo
echo "=== compose config (syntax only, no apply) ==="
docker compose -f docker-compose.yml config --quiet \
&& echo "OK: docker-compose.yml is valid" \
|| echo "WARN: compose validation failed (non-fatal for now)"
else
echo "docker not available in this build environment, skipping"
fi
'''
}
}
}
post {
success { echo "SUCCESS — no deployment performed (by design)" }
failure { echo "FAILED — check logs" }
}
}
+11 -5
View File
@@ -656,7 +656,8 @@ async def job_historical_raw(count: int = 0, max_pages: int = 100):
async def job_process_raw(batch_size: int = 200): async def job_process_raw(batch_size: int = 200):
"""news_raw의 미처리 행을 batch_size만큼 EXAONE 분석 → news_analysis로 이동""" """news_raw의 미처리 행을 batch_size만큼 EXAONE 분석 → news_analysis로 이동.
OLLAMA_NUM_PARALLEL=4와 매칭되는 4-동시 병렬 처리."""
async with pg_pool.acquire() as conn: async with pg_pool.acquire() as conn:
rows = await conn.fetch(""" rows = await conn.fetch("""
SELECT id, stock_code, title, url, source, published_at_text SELECT id, stock_code, title, url, source, published_at_text
@@ -666,8 +667,11 @@ async def job_process_raw(batch_size: int = 200):
if not rows: if not rows:
logger.info("process_raw.empty"); return 0 logger.info("process_raw.empty"); return 0
ok = 0 ok = 0
sem = asyncio.Semaphore(4)
async with httpx.AsyncClient() as c: async with httpx.AsyncClient() as c:
for r in rows: async def _one(r):
nonlocal ok
async with sem:
item = { item = {
"title": r["title"], "url": r["url"], "title": r["title"], "url": r["url"],
"source": r["source"] or "네이버금융", "source": r["source"] or "네이버금융",
@@ -691,6 +695,7 @@ async def job_process_raw(batch_size: int = 200):
except Exception as e: except Exception as e:
stats.errors += 1 stats.errors += 1
logger.warning("process_raw.err", id=r["id"], error=str(e)) logger.warning("process_raw.err", id=r["id"], error=str(e))
await asyncio.gather(*(_one(r) for r in rows))
logger.info("process_raw.batch_done", batch=len(rows), processed_ok=ok) logger.info("process_raw.batch_done", batch=len(rows), processed_ok=ok)
return ok return ok
@@ -818,9 +823,10 @@ async def startup():
scheduler.add_job(job_rss,"cron",day_of_week="sat,sun",hour="8-22",minute="*/15",id="rss_weekend",replace_existing=True) scheduler.add_job(job_rss,"cron",day_of_week="sat,sun",hour="8-22",minute="*/15",id="rss_weekend",replace_existing=True)
# 평일 마켓 (네이버 금융): 9-17시 10분마다 # 평일 마켓 (네이버 금융): 9-17시 10분마다
scheduler.add_job(job_market,"cron",day_of_week="mon-fri",hour="9-17",minute="*/10",id="market",replace_existing=True) scheduler.add_job(job_market,"cron",day_of_week="mon-fri",hour="9-17",minute="*/10",id="market",replace_existing=True)
# raw 뉴스 분석: 24시간 30분마다 batch 200건 처리 (백로그 소화용) # raw 뉴스 분석: 30분마다 batch 200건 처리 (병렬화 4-동시로 30분 내 완주)
scheduler.add_job(job_process_raw,"cron",hour="*",minute="*/30", scheduler.add_job(job_process_raw,"cron",minute="*/30",
id="process_raw",replace_existing=True,kwargs={"batch_size":200}) id="process_raw",replace_existing=True,
max_instances=1, kwargs={"batch_size":200})
# 매주 일요일 새벽 2시 raw 백필 (전체 활성종목) # 매주 일요일 새벽 2시 raw 백필 (전체 활성종목)
scheduler.add_job(job_historical_raw,"cron",day_of_week="sun",hour="2",minute="0", scheduler.add_job(job_historical_raw,"cron",day_of_week="sun",hour="2",minute="0",
id="historical_raw_weekly",replace_existing=True, id="historical_raw_weekly",replace_existing=True,
+899 -183
View File
File diff suppressed because it is too large Load Diff