Compare commits
6 Commits
feature/is
...
feature/is
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0146d1bf8a | ||
| 497564e75c | |||
|
|
988a56c07c | ||
| c9f1345e3c | |||
|
|
8c492eae3a | ||
| 271c592a46 |
@@ -179,8 +179,8 @@ class PromptOptimizer:
|
||||
# Minimal instructions
|
||||
prompt = (
|
||||
f"{market_name} trader. Analyze:\n{data_str}\n\n"
|
||||
'Return JSON: {"act":"BUY"|"SELL"|"HOLD","conf":<0-100>,"reason":"<text>"}\n'
|
||||
"Rules: act=BUY/SELL/HOLD, conf=0-100, reason=concise. No markdown."
|
||||
'Return JSON: {"action":"BUY"|"SELL"|"HOLD","confidence":<0-100>,"rationale":"<text>"}\n'
|
||||
"Rules: action=BUY/SELL/HOLD, confidence=0-100, rationale=concise. No markdown."
|
||||
)
|
||||
else:
|
||||
# Data only (for cached contexts where instructions are known)
|
||||
|
||||
@@ -430,7 +430,7 @@ class KISBroker:
|
||||
"fid_cond_mrkt_div_code": "J",
|
||||
"fid_cond_scr_div_code": "20170",
|
||||
"fid_input_iscd": "0000",
|
||||
"fid_rank_sort_cls_code": "0000",
|
||||
"fid_rank_sort_cls_code": "0",
|
||||
"fid_input_cnt_1": str(limit),
|
||||
"fid_prc_cls_code": "0",
|
||||
"fid_input_price_1": "0",
|
||||
@@ -466,7 +466,7 @@ class KISBroker:
|
||||
rankings = []
|
||||
for item in data.get("output", [])[:limit]:
|
||||
rankings.append({
|
||||
"stock_code": item.get("mksc_shrn_iscd", ""),
|
||||
"stock_code": item.get("stck_shrn_iscd") or item.get("mksc_shrn_iscd", ""),
|
||||
"name": item.get("hts_kor_isnm", ""),
|
||||
"price": _safe_float(item.get("stck_prpr", "0")),
|
||||
"volume": _safe_float(item.get("acml_vol", "0")),
|
||||
|
||||
@@ -133,7 +133,7 @@ class OverseasBroker:
|
||||
"AUTH": "",
|
||||
"EXCD": ranking_excd,
|
||||
"NDAY": "0",
|
||||
"GUBN": "1",
|
||||
"GUBN": "0", # 0=전체(상승+하락), 1=상승만 — 변동성 스캐너는 전체 필요
|
||||
"VOL_RANG": "0",
|
||||
}
|
||||
|
||||
|
||||
@@ -13,10 +13,11 @@ from fastapi import FastAPI, HTTPException, Query
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
|
||||
def create_dashboard_app(db_path: str) -> FastAPI:
|
||||
def create_dashboard_app(db_path: str, mode: str = "paper") -> FastAPI:
|
||||
"""Create dashboard FastAPI app bound to a SQLite database path."""
|
||||
app = FastAPI(title="The Ouroboros Dashboard", version="1.0.0")
|
||||
app.state.db_path = db_path
|
||||
app.state.mode = mode
|
||||
|
||||
@app.get("/")
|
||||
def index() -> FileResponse:
|
||||
@@ -111,7 +112,7 @@ def create_dashboard_app(db_path: str) -> FastAPI:
|
||||
|
||||
return {
|
||||
"date": today,
|
||||
"mode": os.getenv("MODE", "paper"),
|
||||
"mode": mode,
|
||||
"markets": market_status,
|
||||
"totals": {
|
||||
"trade_count": total_trades,
|
||||
|
||||
@@ -2045,7 +2045,7 @@ def _start_dashboard_server(settings: Settings) -> threading.Thread | None:
|
||||
import uvicorn
|
||||
from src.dashboard import create_dashboard_app
|
||||
|
||||
app = create_dashboard_app(settings.DB_PATH)
|
||||
app = create_dashboard_app(settings.DB_PATH, mode=settings.MODE)
|
||||
uvicorn.run(
|
||||
app,
|
||||
host=settings.DASHBOARD_HOST,
|
||||
|
||||
@@ -354,6 +354,8 @@ class TestFetchMarketRankings:
|
||||
assert "ranking/fluctuation" in url
|
||||
assert headers.get("tr_id") == "FHPST01700000"
|
||||
assert params.get("fid_cond_scr_div_code") == "20170"
|
||||
# 실전 API는 4자리("0000") 거부 — 1자리("0")여야 한다 (#240)
|
||||
assert params.get("fid_rank_sort_cls_code") == "0"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_volume_returns_parsed_rows(self, broker: KISBroker) -> None:
|
||||
@@ -376,6 +378,27 @@ class TestFetchMarketRankings:
|
||||
assert result[0]["price"] == 75000.0
|
||||
assert result[0]["change_rate"] == 2.5
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fluctuation_parses_stck_shrn_iscd(self, broker: KISBroker) -> None:
|
||||
"""실전 API는 mksc_shrn_iscd 대신 stck_shrn_iscd를 반환한다 (#240)."""
|
||||
items = [
|
||||
{
|
||||
"stck_shrn_iscd": "015260",
|
||||
"hts_kor_isnm": "에이엔피",
|
||||
"stck_prpr": "794",
|
||||
"acml_vol": "4896196",
|
||||
"prdy_ctrt": "29.74",
|
||||
"vol_inrt": "0",
|
||||
}
|
||||
]
|
||||
mock_resp = _make_ranking_mock(items)
|
||||
with patch("aiohttp.ClientSession.get", return_value=mock_resp):
|
||||
result = await broker.fetch_market_rankings(ranking_type="fluctuation")
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0]["stock_code"] == "015260"
|
||||
assert result[0]["change_rate"] == 29.74
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# KRX tick unit / round-down helpers (issue #157)
|
||||
|
||||
@@ -415,28 +415,37 @@ def test_status_circuit_breaker_unknown_when_no_data(tmp_path: Path) -> None:
|
||||
assert cb["current_pnl_pct"] is None
|
||||
|
||||
|
||||
def test_status_mode_paper(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""MODE=paper일 때 status 응답에 mode=paper가 포함돼야 한다."""
|
||||
monkeypatch.setenv("MODE", "paper")
|
||||
app = _app(tmp_path)
|
||||
def test_status_mode_paper(tmp_path: Path) -> None:
|
||||
"""mode=paper로 생성하면 status 응답에 mode=paper가 포함돼야 한다."""
|
||||
db_path = tmp_path / "dashboard_test.db"
|
||||
conn = init_db(str(db_path))
|
||||
_seed_db(conn)
|
||||
conn.close()
|
||||
app = create_dashboard_app(str(db_path), mode="paper")
|
||||
get_status = _endpoint(app, "/api/status")
|
||||
body = get_status()
|
||||
assert body["mode"] == "paper"
|
||||
|
||||
|
||||
def test_status_mode_live(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""MODE=live일 때 status 응답에 mode=live가 포함돼야 한다."""
|
||||
monkeypatch.setenv("MODE", "live")
|
||||
app = _app(tmp_path)
|
||||
def test_status_mode_live(tmp_path: Path) -> None:
|
||||
"""mode=live로 생성하면 status 응답에 mode=live가 포함돼야 한다."""
|
||||
db_path = tmp_path / "dashboard_test.db"
|
||||
conn = init_db(str(db_path))
|
||||
_seed_db(conn)
|
||||
conn.close()
|
||||
app = create_dashboard_app(str(db_path), mode="live")
|
||||
get_status = _endpoint(app, "/api/status")
|
||||
body = get_status()
|
||||
assert body["mode"] == "live"
|
||||
|
||||
|
||||
def test_status_mode_default_paper(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""MODE 환경변수가 없으면 mode 기본값은 paper여야 한다."""
|
||||
monkeypatch.delenv("MODE", raising=False)
|
||||
app = _app(tmp_path)
|
||||
def test_status_mode_default_paper(tmp_path: Path) -> None:
|
||||
"""mode 파라미터 미전달 시 기본값은 paper여야 한다."""
|
||||
db_path = tmp_path / "dashboard_test.db"
|
||||
conn = init_db(str(db_path))
|
||||
_seed_db(conn)
|
||||
conn.close()
|
||||
app = create_dashboard_app(str(db_path))
|
||||
get_status = _endpoint(app, "/api/status")
|
||||
body = get_status()
|
||||
assert body["mode"] == "paper"
|
||||
|
||||
@@ -124,7 +124,7 @@ class TestFetchOverseasRankings:
|
||||
assert "/uapi/overseas-stock/v1/ranking/updown-rate" in url
|
||||
assert params["EXCD"] == "NAS"
|
||||
assert params["NDAY"] == "0"
|
||||
assert params["GUBN"] == "1"
|
||||
assert params["GUBN"] == "0" # 0=전체(상승+하락), 변동성 스캐너에 필요
|
||||
assert params["VOL_RANG"] == "0"
|
||||
|
||||
overseas_broker._broker._auth_headers.assert_called_with("HHDFS76290000")
|
||||
|
||||
@@ -124,6 +124,10 @@ class TestPromptOptimizer:
|
||||
assert len(prompt) < 300
|
||||
assert "005930" in prompt
|
||||
assert "75000" in prompt
|
||||
# Keys must match parse_response expectations (#242)
|
||||
assert '"action"' in prompt
|
||||
assert '"confidence"' in prompt
|
||||
assert '"rationale"' in prompt
|
||||
|
||||
def test_build_compressed_prompt_no_instructions(self):
|
||||
"""Test compressed prompt without instructions."""
|
||||
|
||||
Reference in New Issue
Block a user