Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b4caa95f1 | |||
| 99c4885692 | |||
| 74a83f94fb | |||
| 1e6bf702bc | |||
| e0508324e5 | |||
| 9e6e2ded2f | |||
| 8f13bb4a48 | |||
| 7e0c0b6c8f | |||
| 18d78a9f04 | |||
| f72d796636 | |||
| ebb863371d | |||
| ad17e7dae1 | |||
| a1bbeb99a6 | |||
| 15c7971018 | |||
| 6051338367 | |||
| 3e7ea1d007 | |||
| 10e1cfe409 |
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"scriptId": "1xfeBAeeknmnBtSvrIqWXO_2hc3ByeriLUOSuOOB4YxLLHhN3zdnL7tVh",
|
|
||||||
"projectId": "1072944905499",
|
|
||||||
"rootDir": "Temp/gas_deploy"
|
|
||||||
}
|
|
||||||
@@ -151,6 +151,69 @@ jobs:
|
|||||||
- name: Validate DB First Pipeline
|
- name: Validate DB First Pipeline
|
||||||
run: python3 tools/validate_db_first_pipeline_v1.py
|
run: python3 tools/validate_db_first_pipeline_v1.py
|
||||||
|
|
||||||
|
- name: Update Proposal Evaluation History
|
||||||
|
run: python3 tools/update_proposal_evaluation_history.py --json GatherTradingData.json --history Temp/proposal_evaluation_history.json
|
||||||
|
|
||||||
|
- name: Build Performance Readiness Replay Bridge
|
||||||
|
run: python3 tools/build_performance_readiness_replay_bridge_v1.py --hist Temp/proposal_evaluation_history.json --out Temp/performance_readiness_replay_bridge_v1.json
|
||||||
|
|
||||||
|
- name: Build Outcome Quality Score
|
||||||
|
run: python3 tools/build_outcome_quality_score_v1.py --json GatherTradingData.json --out Temp/outcome_quality_score_v1.json --policy spec/strategy_execution_lock_policy.yaml
|
||||||
|
|
||||||
|
- name: Build Trade Quality From T5
|
||||||
|
run: python3 tools/build_trade_quality_from_t5_v1.py --hist Temp/proposal_evaluation_history.json --out Temp/trade_quality_from_t5_v1.json
|
||||||
|
|
||||||
|
- name: Build Operational Alpha Calibration
|
||||||
|
run: python3 tools/build_operational_alpha_calibration_v2.py --out Temp/operational_alpha_calibration_v2.json
|
||||||
|
|
||||||
|
- name: Validate Operational Alpha Calibration
|
||||||
|
run: python3 tools/validate_operational_alpha_calibration_v2.py --input Temp/operational_alpha_calibration_v2.json --out Temp/validate_operational_alpha_calibration_v2.json
|
||||||
|
|
||||||
|
- name: Build Operational T20 Outcome Ledger
|
||||||
|
run: python3 tools/build_operational_t20_outcome_ledger_v1.py --json GatherTradingData.json --out Temp/operational_t20_outcome_ledger_v1.json
|
||||||
|
|
||||||
|
- name: Validate Live Data Activation Gate
|
||||||
|
run: python3 tools/validate_live_data_activation_gate_v1.py
|
||||||
|
|
||||||
|
- name: Validate Replay Live Separation
|
||||||
|
run: python3 tools/validate_replay_live_separation_v1.py
|
||||||
|
|
||||||
|
- name: Render Final Decision Packet V4
|
||||||
|
run: dotnet run --project src/dotnet/QuantEngine.Tools/QuantEngine.Tools.csproj -- packet-v4 --packet=Temp/final_decision_packet_active.json --out=Temp/final_decision_packet_v4.json
|
||||||
|
|
||||||
|
- name: Render Operational Report
|
||||||
|
run: dotnet run --project src/dotnet/QuantEngine.Tools/QuantEngine.Tools.csproj -- report --packet=Temp/final_decision_packet_active.json --out=Temp/operational_report.json
|
||||||
|
|
||||||
|
- name: Validate Report Packet Sync
|
||||||
|
run: python3 tools/validate_report_packet_sync_v1.py --packet Temp/final_decision_packet_active.json --report Temp/operational_report.json | tee Temp/validate_report_packet_sync_v1.json
|
||||||
|
|
||||||
|
- name: Validate Report Section Completeness
|
||||||
|
run: python3 tools/validate_report_section_completeness_v1.py
|
||||||
|
|
||||||
|
- name: Validate JSON Generator Outputs
|
||||||
|
run: python3 tools/validate_json_generator_outputs_v1.py
|
||||||
|
|
||||||
|
- name: Generate PostgreSQL History Schema
|
||||||
|
run: python3 tools/generate_postgresql_history_schema_v1.py
|
||||||
|
|
||||||
|
- name: Validate PostgreSQL History Contract
|
||||||
|
run: python3 tools/validate_postgresql_history_contract_v1.py
|
||||||
|
|
||||||
|
- name: Package Operational Report Artifacts
|
||||||
|
run: tar -czf Temp/operational-report-artifacts.tar.gz Temp/operational_report.json Temp/operational_report.md Temp/missing_data_inventory_v1.json Temp/report_section_completeness.json Temp/operational_alpha_calibration_v2.json Temp/validate_operational_alpha_calibration_v2.json Temp/operational_t20_outcome_ledger_v1.json Temp/live_data_activation_gate_v1.json Temp/replay_live_separation_v1.json Temp/validate_report_packet_sync_v1.json Temp/json_generator_outputs_v1.json Temp/proposal_evaluation_history.json Temp/performance_readiness_replay_bridge_v1.json Temp/postgresql_history_schema_v1.sql Temp/postgresql_history_schema_v1.json Temp/postgresql_history_contract_v1.json
|
||||||
|
|
||||||
|
- name: Upload Operational Report Artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: operational-report-artifacts
|
||||||
|
path: Temp/operational-report-artifacts.tar.gz
|
||||||
|
|
||||||
|
- name: Upload Operational Report JSON
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: operational-report-json
|
||||||
|
path: Temp/operational_report.json
|
||||||
|
|
||||||
validate-ui-and-storage:
|
validate-ui-and-storage:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: validate-core
|
needs: validate-core
|
||||||
|
|||||||
@@ -53,3 +53,21 @@ jobs:
|
|||||||
ssh -i ~/.ssh/id_ed25519 kjh2064@178.104.200.7 "chmod +x /home/kjh2064/tmp/deploy.sh 2>/dev/null || true"
|
ssh -i ~/.ssh/id_ed25519 kjh2064@178.104.200.7 "chmod +x /home/kjh2064/tmp/deploy.sh 2>/dev/null || true"
|
||||||
scp -i ~/.ssh/id_ed25519 tools/deploy_quantengine.sh kjh2064@178.104.200.7:/home/kjh2064/tmp/deploy.sh
|
scp -i ~/.ssh/id_ed25519 tools/deploy_quantengine.sh kjh2064@178.104.200.7:/home/kjh2064/tmp/deploy.sh
|
||||||
ssh -i ~/.ssh/id_ed25519 kjh2064@178.104.200.7 "chmod +x /home/kjh2064/tmp/deploy.sh && /home/kjh2064/tmp/deploy.sh"
|
ssh -i ~/.ssh/id_ed25519 kjh2064@178.104.200.7 "chmod +x /home/kjh2064/tmp/deploy.sh && /home/kjh2064/tmp/deploy.sh"
|
||||||
|
|
||||||
|
- name: Verify Public Routes
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
root_html=$(curl -s "http://178.104.200.7/quant/")
|
||||||
|
ops_html=$(curl -s "http://178.104.200.7/quant/operations")
|
||||||
|
root_code=$(printf '%s' "$root_html" | grep -q "Quant Engine" && echo 200 || echo 500)
|
||||||
|
ops_code=$(printf '%s' "$ops_html" | grep -q "Operational Report" && echo 200 || echo 500)
|
||||||
|
echo "/quant/ -> ${root_code}"
|
||||||
|
echo "/quant/operations -> ${ops_code}"
|
||||||
|
if [ "$root_code" != "200" ]; then
|
||||||
|
echo "Deployment content check failed for /quant/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "$ops_code" != "200" ]; then
|
||||||
|
echo "Deployment content check failed for /quant/operations"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|||||||
+11
@@ -36,3 +36,14 @@ node_modules/
|
|||||||
.claude/projects/
|
.claude/projects/
|
||||||
*.db-shm
|
*.db-shm
|
||||||
*.db-wal
|
*.db-wal
|
||||||
|
|
||||||
|
# 개발자 임시/테스트/백업 파일 패턴 차단
|
||||||
|
**/debug_*.log
|
||||||
|
**/tmp_*.json
|
||||||
|
**/mock_*.json
|
||||||
|
**/*_temp.*
|
||||||
|
**/*.bak
|
||||||
|
**/*.swp
|
||||||
|
**/*_backup*
|
||||||
|
**/*_copy*
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
- `spec/`: source of truth. 공식, 계약, 게이트, 출력 스키마의 최우선 읽기 경로.
|
- `spec/`: source of truth. 공식, 계약, 게이트, 출력 스키마의 최우선 읽기 경로.
|
||||||
- `governance/`: 운영 규칙, 인덱스, 해시 마이그레이션, ADR, 템플릿.
|
- `governance/`: 운영 규칙, 인덱스, 해시 마이그레이션, ADR, 템플릿.
|
||||||
- `src/`: Python canonical implementation. 새 로직은 여기부터 반영한다.
|
- `src/`: Python canonical implementation. 새 로직은 여기부터 반영한다.
|
||||||
|
- `src/dotnet/QuantEngine.Tools`: canonical .NET operational report and packet renderer.
|
||||||
- `src/quant_engine/data_collection_backend_v1.py`: collection backend selector.
|
- `src/quant_engine/data_collection_backend_v1.py`: collection backend selector.
|
||||||
- `src/quant_engine/data_collection_store_v1.py`: SQLite collection store.
|
- `src/quant_engine/data_collection_store_v1.py`: SQLite collection store.
|
||||||
- `src/quant_engine/kis_data_collection_v1.py`: KIS 우선 수집기.
|
- `src/quant_engine/kis_data_collection_v1.py`: KIS 우선 수집기.
|
||||||
@@ -70,6 +71,7 @@
|
|||||||
- `KIS-first`: KIS 우선.
|
- `KIS-first`: KIS 우선.
|
||||||
- `SQLite-first`: SQLite/JSON 우선.
|
- `SQLite-first`: SQLite/JSON 우선.
|
||||||
- `tools/`: build/validate/convert/audit CLI.
|
- `tools/`: build/validate/convert/audit CLI.
|
||||||
|
- `tools/render_operational_report.py`: legacy renderer, 운영/CI 경로에서 사용 금지.
|
||||||
- `tools/run_kis_data_collection_v1.py`: KIS collection thin CLI.
|
- `tools/run_kis_data_collection_v1.py`: KIS collection thin CLI.
|
||||||
- `tools/generate_postgresql_upgrade_stub_v1.py`: PostgreSQL stub generator.
|
- `tools/generate_postgresql_upgrade_stub_v1.py`: PostgreSQL stub generator.
|
||||||
- `tools/validate_platform_transition_wbs_v1.py`: `.gs → Python` and `xlsx → sqlite` WBS validator.
|
- `tools/validate_platform_transition_wbs_v1.py`: `.gs → Python` and `xlsx → sqlite` WBS validator.
|
||||||
@@ -88,10 +90,9 @@
|
|||||||
- `docs/ROADMAP_WBS.md`의 WBS-8.2: `run_kis_data_collection_v1.py` → `validate_platform_transition_wbs_v1.py` → `validate_snapshot_admin_web_v1.py`.
|
- `docs/ROADMAP_WBS.md`의 WBS-8.2: `run_kis_data_collection_v1.py` → `validate_platform_transition_wbs_v1.py` → `validate_snapshot_admin_web_v1.py`.
|
||||||
- `Temp/snapshot_admin_approval_packet_v1.json`: snapshot admin approval packet export.
|
- `Temp/snapshot_admin_approval_packet_v1.json`: snapshot admin approval packet export.
|
||||||
- `Temp/snapshot_admin_approval_packet_v1.md`: snapshot admin approval packet summary.
|
- `Temp/snapshot_admin_approval_packet_v1.md`: snapshot admin approval packet summary.
|
||||||
- `gas_event_calendar.gs`: 이벤트 캘린더 배포 호환 스텁. `seedEventCalendar_()` / `runEventRisk()` 진입점을 유지한다.
|
|
||||||
- `Temp/`: 실행 결과와 캐시. 라우팅 대상은 아니며 runtime consumer만 읽는다.
|
- `Temp/`: 실행 결과와 캐시. 라우팅 대상은 아니며 runtime consumer만 읽는다.
|
||||||
- `DB 파일 관리`: workspace/collector DB는 단일 canonical 경로만 사용한다. 동일 역할의 SQLite 파일을 `src/`와 `outputs/`에 중복 생성하지 말고, 실행 기본값·README·WBS·검증 스크립트가 같은 경로를 가리키게 유지한다. 임시 검증 DB는 `Temp/`에만 두고, 운영 기준 DB로 승격할 때는 명시적으로 문서화한다. canonical workspace DB는 `src/quant_engine/snapshot_admin.db`이며, 다른 위치의 동일 역할 DB는 파생/아카이브/마이그레이션 전용으로만 취급한다. 운영 진입점과 일반 검증 스크립트는 canonical 파일만 읽고 써야 한다.
|
- `DB 파일 관리`: workspace/collector DB는 단일 canonical 경로만 사용한다. 동일 역할의 SQLite 파일을 `src/`와 `outputs/`에 중복 생성하지 말고, 실행 기본값·README·WBS·검증 스크립트가 같은 경로를 가리키게 유지한다. 임시 검증 DB는 `Temp/`에만 두고, 운영 기준 DB로 승격할 때는 명시적으로 문서화한다. canonical workspace DB는 `src/quant_engine/snapshot_admin.db`이며, 다른 위치의 동일 역할 DB는 파생/아카이브/마이그레이션 전용으로만 취급한다. 운영 진입점과 일반 검증 스크립트는 canonical 파일만 읽고 써야 한다.
|
||||||
- `docs/archive/`, `suggest/`, `artifacts/archive/`: 문서 검색/색인 제외 대상. 감사나 이력 추적이 필요할 때만 명시적으로 읽는다.
|
- `docs/archive/`, `docs/legacy/`, `suggest/`, `artifacts/archive/`, `src/quant_engine/deprecated/`: 문서 및 폐기된 파이썬 코드 검색/색인 제외 대상. 감사나 이력 추적이 필요할 때만 명시적으로 읽는다.
|
||||||
- `dist/`, `artifacts/`, `docs/`, `examples/`, `prompts/`, `schemas/`, `tests/`: 패키징/문서/검증/산출물 보조 경로.
|
- `dist/`, `artifacts/`, `docs/`, `examples/`, `prompts/`, `schemas/`, `tests/`: 패키징/문서/검증/산출물 보조 경로.
|
||||||
- `run_all`: 외부 스케줄러가 호출하는 진입점으로 유지한다. 실행 시 `run_all_invocation_mode=external_scheduler`를 기준으로 해석한다.
|
- `run_all`: 외부 스케줄러가 호출하는 진입점으로 유지한다. 실행 시 `run_all_invocation_mode=external_scheduler`를 기준으로 해석한다.
|
||||||
|
|
||||||
@@ -131,6 +132,7 @@
|
|||||||
- `python` → Python 3.13.5 (`Python313/`) — yaml/openpyxl/yfinance 등 프로젝트 패키지 설치됨
|
- `python` → Python 3.13.5 (`Python313/`) — yaml/openpyxl/yfinance 등 프로젝트 패키지 설치됨
|
||||||
- `python3` → Python 3.12 (Windows Store) — 프로젝트 패키지 미설치 → `ModuleNotFoundError` 유발
|
- `python3` → Python 3.12 (Windows Store) — 프로젝트 패키지 미설치 → `ModuleNotFoundError` 유발
|
||||||
- 클라우드 서버(hz-prod-01)는 `/usr/bin/python3`를 사용하므로 `.gitea/workflows/ci.yml`은 `python3` 유지
|
- 클라우드 서버(hz-prod-01)는 `/usr/bin/python3`를 사용하므로 `.gitea/workflows/ci.yml`은 `python3` 유지
|
||||||
|
- **임시 파일 관리**: 개발/디버깅 목적의 모든 휘발성 임시 파일 및 로그는 반드시 `Temp/` 디렉토리 하위에서만 생성해야 하며, 루트나 다른 패키지 경로에 임시 파일을 만드는 것은 금지한다. 불가피하게 생성할 경우 반드시 접두사/접미사 규칙(`debug_*`, `tmp_*`, `mock_*`, `*_temp.*`)을 준수하여 `.gitignore`에 필터링되도록 한다.
|
||||||
|
|
||||||
## 6. 검증 규칙
|
## 6. 검증 규칙
|
||||||
- `python tools/validate_specs.py`
|
- `python tools/validate_specs.py`
|
||||||
@@ -143,7 +145,6 @@
|
|||||||
|
|
||||||
## 6b. 추가 운영 헌법 원칙 (proposed_AGENTS_constitution_v1 반영)
|
## 6b. 추가 운영 헌법 원칙 (proposed_AGENTS_constitution_v1 반영)
|
||||||
- Live T+20 표본이 30건 미만이면 `active` 또는 `PASS_100`으로 승격하지 않는다.
|
- Live T+20 표본이 30건 미만이면 `active` 또는 `PASS_100`으로 승격하지 않는다.
|
||||||
- GAS는 투자 판단 로직을 새로 받아서는 안 된다 (thin adapter 원칙 — `ADR-0002`).
|
|
||||||
- 프롬프트가 LLM에게 가격·수량·임계값·점수를 직접 계산하도록 요청하는 것을 금지한다.
|
- 프롬프트가 LLM에게 가격·수량·임계값·점수를 직접 계산하도록 요청하는 것을 금지한다.
|
||||||
- 하네스 FAIL 상태를 실행 가능한 주문 표로 렌더링하지 않는다.
|
- 하네스 FAIL 상태를 실행 가능한 주문 표로 렌더링하지 않는다.
|
||||||
- 최종 결정 권한은 단일 캐노니컬 실행 패킷(`final_decision_packet_active.json`)에서만 나온다.
|
- 최종 결정 권한은 단일 캐노니컬 실행 패킷(`final_decision_packet_active.json`)에서만 나온다.
|
||||||
|
|||||||
@@ -141,12 +141,23 @@ npm run prepare-upload-zip
|
|||||||
4. `GatherTradingData.xlsx` 의존성을 제거한 후에도 수집이 유지되는지 확인
|
4. `GatherTradingData.xlsx` 의존성을 제거한 후에도 수집이 유지되는지 확인
|
||||||
5. 이후 PostgreSQL 업그레이드 시 동일 row contract를 유지
|
5. 이후 PostgreSQL 업그레이드 시 동일 row contract를 유지
|
||||||
|
|
||||||
|
## CI / 배포 분리
|
||||||
|
|
||||||
|
- `.gitea/workflows/ci.yml`은 검증 전용이다.
|
||||||
|
- `.gitea/workflows/snapshot_admin_deploy.yml`은 실배포 전용이다.
|
||||||
|
- 공개 URL `http://178.104.200.7/quant/` 갱신은 deploy workflow 성공 여부로 판단한다.
|
||||||
|
- Gitea 토큰은 문서에 값으로 적지 않고 `GITEA_TOKEN_TAXBAIK` 같은 환경변수/secret 이름으로만 관리한다.
|
||||||
|
|
||||||
## 운영 리포트 계약
|
## 운영 리포트 계약
|
||||||
|
|
||||||
운영 리포트는 사람이 읽는 `Temp/operational_report.md`와 기계 검증용 `Temp/operational_report.json`을 함께 생성합니다.
|
운영 리포트는 .NET canonical renderer가 사람이 읽는 `Temp/operational_report.md`와 기계 검증용 `Temp/operational_report.json`을 함께 생성합니다.
|
||||||
|
운영 상태와 legacy 분리는 [DOTNET_RENDERER_OPERATING_STATUS.md](/C:/Temp/data_feed/docs/DOTNET_RENDERER_OPERATING_STATUS.md)에서 확인합니다.
|
||||||
|
|
||||||
|
- `src/dotnet/QuantEngine.Tools/Program.cs`가 canonical 생성 경로입니다.
|
||||||
|
- `npm run render-report-json`도 같은 .NET 경로를 호출합니다.
|
||||||
- `operational_report.json`이 canonical 계약입니다.
|
- `operational_report.json`이 canonical 계약입니다.
|
||||||
- `operational_report.md`는 표시용 렌더입니다.
|
- `operational_report.md`는 표시용 렌더입니다.
|
||||||
|
- `Temp/missing_data_inventory_v1.json`은 `DATA_MISSING` 섹션 분리 인벤토리입니다.
|
||||||
- JSON 스키마는 `schemas/operational_report.schema.json`을 사용합니다.
|
- JSON 스키마는 `schemas/operational_report.schema.json`을 사용합니다.
|
||||||
- 계약 드리프트 검사는 `npm run validate-operational-report-contract`로 수행합니다.
|
- 계약 드리프트 검사는 `npm run validate-operational-report-contract`로 수행합니다.
|
||||||
- 전체 게이트에는 `render-report-json -> validate-report-json -> validate-report-quality -> validate-report-sync` 순서가 포함됩니다.
|
- 전체 게이트에는 `render-report-json -> validate-report-json -> validate-report-quality -> validate-report-sync` 순서가 포함됩니다.
|
||||||
|
|||||||
@@ -228,6 +228,12 @@ services:
|
|||||||
|
|
||||||
> 총 6개 러너가 활성 상태. 네트워크는 `gitea_default` Docker 네트워크 사용.
|
> 총 6개 러너가 활성 상태. 네트워크는 `gitea_default` Docker 네트워크 사용.
|
||||||
|
|
||||||
|
### 6.4. CI / 배포 분리
|
||||||
|
|
||||||
|
- `.gitea/workflows/ci.yml`: 검증 전용. 스펙/공식/리포트/아티팩트 생성까지만 수행한다.
|
||||||
|
- `.gitea/workflows/snapshot_admin_deploy.yml`: 실배포 전용. `dotnet publish` 후 `tools/deploy_quantengine.sh`를 이용해 `/home/kjh2064/quantengine_active`로 반영한다.
|
||||||
|
- 공개 URL `/quant/` 갱신은 `snapshot_admin_deploy.yml`의 성공 여부를 기준으로 판단한다.
|
||||||
|
|
||||||
### 6.2. 러너 설정
|
### 6.2. 러너 설정
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
### 1️⃣ 신호 발생 시 (거래 진입 시점)
|
### 1️⃣ 신호 발생 시 (거래 진입 시점)
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Python 또는 GAS 콘솔에서 실행
|
# Python 또는 DB 마이그레이션 도구에서 실행
|
||||||
signal = {
|
signal = {
|
||||||
"date": "2026-06-25",
|
"date": "2026-06-25",
|
||||||
"ticker": "000660", # SK하이닉스 등
|
"ticker": "000660", # SK하이닉스 등
|
||||||
@@ -25,14 +25,13 @@ signal = {
|
|||||||
"notes": "MA20 돌파 + 스마트머니 매수"
|
"notes": "MA20 돌파 + 스마트머니 매수"
|
||||||
}
|
}
|
||||||
|
|
||||||
# GAS: addSignal_(signal)
|
# 운영 표준: PostgreSQL의 signal/factor history 테이블에 적재
|
||||||
# 또는 스프레드시트에 직접 입력
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**✅ 체크리스트:**
|
**✅ 체크리스트:**
|
||||||
- [ ] signal_id 자동 생성됨 (YYYYMMDD_HHMM 형식)
|
- [ ] signal_id 자동 생성됨 (YYYYMMDD_HHMM 형식)
|
||||||
- [ ] validation_status = "UNVALIDATED"
|
- [ ] validation_status = "UNVALIDATED"
|
||||||
- [ ] 스프레드시트 행 추가됨
|
- [ ] PostgreSQL 이력 행 추가됨
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ signal = {
|
|||||||
**해야 할 일:**
|
**해야 할 일:**
|
||||||
1. T+5일의 종가 조회
|
1. T+5일의 종가 조회
|
||||||
2. `updatePriceT5_(signalId, priceT5)` 실행
|
2. `updatePriceT5_(signalId, priceT5)` 실행
|
||||||
3. 또는 스프레드시트 "price_t5" 열에 직접 입력
|
3. 또는 PostgreSQL `price_t5` 이력 열에 직접 입력
|
||||||
|
|
||||||
**예시:**
|
**예시:**
|
||||||
```
|
```
|
||||||
@@ -264,8 +263,9 @@ T+20 종가: 51,050원
|
|||||||
|
|
||||||
## 🔗 관련 문서
|
## 🔗 관련 문서
|
||||||
|
|
||||||
- `spec/realtime/live_outcome_ledger_plan.yaml` — 마스터 계획
|
- `spec/realtime/live_outcome_ledger_plan.yaml` — 마스터 계획(역사적)
|
||||||
- `src/google_apps_script/live_outcome_ledger.gs` — GAS 코드
|
- `src/google_apps_script/live_outcome_ledger.gs` — 역사적 GAS 원장 어댑터
|
||||||
|
- `spec/02_data_contract.yaml` — PostgreSQL history-first 운영 계약
|
||||||
- `V9_HARDENING_IMPLEMENTATION_ROADMAP.md` — 전체 로드맵
|
- `V9_HARDENING_IMPLEMENTATION_ROADMAP.md` — 전체 로드맵
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# .NET Renderer Operating Status
|
||||||
|
|
||||||
|
## Current Canonical Path
|
||||||
|
|
||||||
|
- `src/dotnet/QuantEngine.Tools/Program.cs`
|
||||||
|
- `src/dotnet/QuantEngine.Tools/QuantEngine.Tools.csproj`
|
||||||
|
|
||||||
|
## Current Outputs
|
||||||
|
|
||||||
|
- `Temp/operational_report.json`
|
||||||
|
- `Temp/operational_report.md`
|
||||||
|
- `Temp/final_decision_packet_v4.json`
|
||||||
|
|
||||||
|
## Legacy Path
|
||||||
|
|
||||||
|
- `tools/render_operational_report.py`
|
||||||
|
|
||||||
|
This file is retained only for historical compatibility and maintenance reference.
|
||||||
|
It is not used in the operating or CI path.
|
||||||
|
|
||||||
|
## Operational Rules
|
||||||
|
|
||||||
|
- CI and release flows must use the .NET renderer path.
|
||||||
|
- Report consumers may continue to read `Temp/operational_report.md` and `Temp/operational_report.json`.
|
||||||
|
- The Python renderer should not be reintroduced into the operating path.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `dotnet build src/dotnet/QuantEngine.sln -c Debug`
|
||||||
|
- `python tools/validate_json_generator_outputs_v1.py`
|
||||||
|
- `python tools/validate_report_packet_sync_v1.py --packet Temp/final_decision_packet_active.json --report Temp/operational_report.json`
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# PostgreSQL History-First Operating Model
|
||||||
|
|
||||||
|
## 목적
|
||||||
|
|
||||||
|
운영 이력, 원천 팩터, 파생 팩터, 최종 판단, 시장-엔진 괴리를 PostgreSQL에 영구 이력으로 적재한다.
|
||||||
|
|
||||||
|
## 원칙
|
||||||
|
|
||||||
|
- PostgreSQL이 canonical operating history store다.
|
||||||
|
- Excel workbook과 Google Apps Script는 운영 소스가 아니다.
|
||||||
|
- 모든 파생 결과는 versioned snapshot과 provenance를 가져야 한다.
|
||||||
|
- 시장 raw와 엔진 결과의 괴리는 별도 gap history로 남긴다.
|
||||||
|
|
||||||
|
## 이력 도메인
|
||||||
|
|
||||||
|
- `market_raw_history`
|
||||||
|
- `factor_version_history`
|
||||||
|
- `factor_output_history`
|
||||||
|
- `decision_result_history`
|
||||||
|
- `market_vs_engine_gap_history`
|
||||||
|
|
||||||
|
## 운영 규칙
|
||||||
|
|
||||||
|
- Append-only를 기본으로 하고, 정정은 correction row로만 남긴다.
|
||||||
|
- 최종 팩터와 최종 판단은 항상 `source_version`을 포함한다.
|
||||||
|
- DB snapshot이 존재하면 리포트와 생성기는 이를 1차 진실원천으로 사용한다.
|
||||||
|
|
||||||
|
## 폐기 대상
|
||||||
|
|
||||||
|
- 운영 경로의 Excel 시트 의존
|
||||||
|
- 운영 경로의 GAS 의사결정/원장 갱신
|
||||||
|
|
||||||
+27
-23
@@ -14,6 +14,7 @@
|
|||||||
3. `WBS-7.8` ETF NAV/괴리율/추적오차/AUM 수집 경로 확정
|
3. `WBS-7.8` ETF NAV/괴리율/추적오차/AUM 수집 경로 확정
|
||||||
4. `WBS-7.5` 임시 하드코딩 폴백 비례화의 실증 보정
|
4. `WBS-7.5` 임시 하드코딩 폴백 비례화의 실증 보정
|
||||||
5. `WBS-7.6` 슬리피지 실측 보정
|
5. `WBS-7.6` 슬리피지 실측 보정
|
||||||
|
6. `WBS-7.9` PostgreSQL history-first operating model 전환
|
||||||
|
|
||||||
`WBS-7.2`, `WBS-7.3`, `WBS-7.4`, `WBS-7.10`~`WBS-7.14`는 현재 문서상 완료 또는 정리 완료로 유지한다.
|
`WBS-7.2`, `WBS-7.3`, `WBS-7.4`, `WBS-7.10`~`WBS-7.14`는 현재 문서상 완료 또는 정리 완료로 유지한다.
|
||||||
|
|
||||||
@@ -745,7 +746,7 @@ python tools/build_qualitative_sell_inputs_v1.py --batch --workbook GatherTradin
|
|||||||
runtime 파생 뷰임을 gas_lib.gs:2010-2081(runEventRisk)·spec/14_raw_workbook_mapping.yaml:415에서
|
runtime 파생 뷰임을 gas_lib.gs:2010-2081(runEventRisk)·spec/14_raw_workbook_mapping.yaml:415에서
|
||||||
확인. data_feed 원자료/결정컬럼과 동일한 "원본 vs 파생" 패턴 — 둘 다 유지.
|
확인. data_feed 원자료/결정컬럼과 동일한 "원본 vs 파생" 패턴 — 둘 다 유지.
|
||||||
⚠️ stale 발견(깨진 게 아님): sector_universe_refresh_audit(16행, 1열 깨진 한글)는 죽은 시트가
|
⚠️ stale 발견(깨진 게 아님): sector_universe_refresh_audit(16행, 1열 깨진 한글)는 죽은 시트가
|
||||||
아니라 gas_lib.gs:writeSectorUniverseRefreshAuditSheet_()·tools/render_operational_report.py가
|
아니라 gas_lib.gs:writeSectorUniverseRefreshAuditSheet_()·src/dotnet/QuantEngine.Tools가
|
||||||
실제로 쓰는 활성 시트다 — xlsx가 최신 15컬럼 영문 스키마로 갱신되지 않은 채 방치된 것뿐.
|
실제로 쓰는 활성 시트다 — xlsx가 최신 15컬럼 영문 스키마로 갱신되지 않은 채 방치된 것뿐.
|
||||||
`python tools/update_sector_universe_from_naver.py --limit 3`(dry-run)으로 정상 스키마(13섹터,
|
`python tools/update_sector_universe_from_naver.py --limit 3`(dry-run)으로 정상 스키마(13섹터,
|
||||||
39행) 생성 가능함을 확인 — `--apply`는 운영 워크북을 덮어쓰는 작업이라 사용자 승인 필요(미실행).
|
39행) 생성 가능함을 확인 — `--apply`는 운영 워크북을 덮어쓰는 작업이라 사용자 승인 필요(미실행).
|
||||||
@@ -1377,9 +1378,9 @@ WBS-8.8 (KIS 리팩터) — 독립적 (원격 병행)
|
|||||||
|
|
||||||
### WBS-10: C#/.NET 엔진 고도화 (Phase 10, 2026-06~12)
|
### WBS-10: C#/.NET 엔진 고도화 (Phase 10, 2026-06~12)
|
||||||
|
|
||||||
> 현황 진단(2026-06-25): .NET 프로젝트는 Python 엔진(41 모듈, 14,500 LOC) 대비 5~10%(~1,400 LOC) 수준.
|
> 현황 진단(2026-06-26): .NET 프로젝트는 Python 엔진(41 모듈, 14,500 LOC) 대비 5~10%(~1,400 LOC) 수준.
|
||||||
> Domain 계산기 6개·데이터 모델 8개·KIS/Naver/Yahoo 클라이언트·PostgreSQL 마이그레이션·Blazor 대시보드 기본 구현 완료.
|
> Domain 계산기 6개·데이터 모델 8개·KIS/Naver/Yahoo 클라이언트·PostgreSQL 마이그레이션·Blazor 대시보드 기본 구현 완료.
|
||||||
> **미구현**: Application 레이어(빈 Class1.cs), 테스트(빈 UnitTest1.cs + Core 참조 누락), 공식 엔진, 하네스 주입, 파이프라인 오케스트레이터.
|
> **미구현**: Application 서비스 일부, 공식 엔진, 하네스 주입, 파이프라인 오케스트레이터.
|
||||||
> **발견된 결함 5건**: D1) Tests.csproj Core ProjectReference 누락, D2) Tests sln 미등록, D3) appsettings.json 비밀번호 하드코딩, D4) NU1510 불필요 패키지, D5) Class1.cs placeholder 2개.
|
> **발견된 결함 5건**: D1) Tests.csproj Core ProjectReference 누락, D2) Tests sln 미등록, D3) appsettings.json 비밀번호 하드코딩, D4) NU1510 불필요 패키지, D5) Class1.cs placeholder 2개.
|
||||||
|
|
||||||
#### WBS-10 의존성 차트
|
#### WBS-10 의존성 차트
|
||||||
@@ -1404,16 +1405,16 @@ WBS-10.1 (기반 결함 수정)
|
|||||||
| 항목 | 내용 |
|
| 항목 | 내용 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| **작업** | 테스트 프로젝트 참조 복원, sln 등록, 불필요 패키지 제거, placeholder 삭제, 비밀번호 환경변수화 |
|
| **작업** | 테스트 프로젝트 참조 복원, sln 등록, 불필요 패키지 제거, placeholder 삭제, 비밀번호 환경변수화 |
|
||||||
| **현재 상태** | Core.Tests에 ProjectReference 없음, sln 미등록, appsettings.json 비밀번호 하드코딩, NU1510 경고 2건, Class1.cs 2개 잔존 |
|
| **현재 상태** | Core.Tests에 Core/Infrastructure ProjectReference 추가 완료, sln에 Tests 등록 완료, appsettings.json 비밀번호는 유지(운영 후속 조치), Class1.cs placeholder 0개, build 경고 0 |
|
||||||
| **담당 파일** | `src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj`, `src/dotnet/QuantEngine.sln`, `src/dotnet/QuantEngine.Infrastructure/QuantEngine.Infrastructure.csproj`, `src/dotnet/QuantEngine.Web/appsettings.json`, `src/dotnet/QuantEngine.Core/Class1.cs`, `src/dotnet/QuantEngine.Infrastructure/Class1.cs` |
|
| **담당 파일** | `src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj`, `src/dotnet/QuantEngine.sln`, `src/dotnet/QuantEngine.Infrastructure/QuantEngine.Infrastructure.csproj`, `src/dotnet/QuantEngine.Web/appsettings.json` |
|
||||||
| **상태** | TODO |
|
| **상태** | 부분 완료 |
|
||||||
|
|
||||||
| 세부 WBS | 작업 | 성공 판단 데이터 | 검증 명령 |
|
| 세부 WBS | 작업 | 성공 판단 데이터 | 검증 명령 |
|
||||||
|----------|------|------------------|----------|
|
|----------|------|------------------|----------|
|
||||||
| 10.1.1 | Core.Tests.csproj에 `<ProjectReference Include="../QuantEngine.Core/QuantEngine.Core.csproj" />` 추가 | csproj 내 ProjectReference 존재 | `dotnet build src/dotnet/QuantEngine.Core.Tests/` → 오류 0 |
|
| 10.1.1 | Core.Tests.csproj에 `<ProjectReference Include="../QuantEngine.Core/QuantEngine.Core.csproj" />` 추가 | csproj 내 ProjectReference 존재 | `dotnet build src/dotnet/QuantEngine.Core.Tests/` → 오류 0 |
|
||||||
| 10.1.2 | QuantEngine.sln에 Core.Tests 프로젝트 등록 | sln 내 Tests 프로젝트 GUID 존재 | `dotnet sln src/dotnet/QuantEngine.sln list` → 5개 프로젝트 출력 |
|
| 10.1.2 | QuantEngine.sln에 Core.Tests 프로젝트 등록 | sln 내 Tests 프로젝트 GUID 존재 | `dotnet sln src/dotnet/QuantEngine.sln list` → 5개 프로젝트 출력 |
|
||||||
| 10.1.3 | Infrastructure.csproj에서 `System.Text.Encoding.CodePages` PackageReference 제거 | NU1510 경고 소멸 | `dotnet build src/dotnet/QuantEngine.sln --verbosity quiet` → 경고 0 |
|
| 10.1.3 | Infrastructure.csproj에서 `System.Text.Encoding.CodePages` PackageReference 제거 | NU1510 경고 소멸 | `dotnet build src/dotnet/QuantEngine.sln --verbosity quiet` → 경고 0 |
|
||||||
| 10.1.4 | Class1.cs placeholder 파일 2개 삭제 (Core/, Infrastructure/) | 파일 미존재 | `Test-Path src/dotnet/QuantEngine.Core/Class1.cs` → False |
|
| 10.1.4 | Class1.cs placeholder 파일 2개 삭제 (Core/, Infrastructure/) | 파일 미존재 | `Test-Path src/dotnet/QuantEngine.Core/Class1.cs` 및 `Test-Path src/dotnet/QuantEngine.Infrastructure/Class1.cs` → False |
|
||||||
| 10.1.5 | appsettings.json 비밀번호 → 환경변수 `ConnectionStrings__DefaultConnection` 또는 `dotnet user-secrets` 전환 | appsettings.json 내 실제 비밀번호 문자열 0건 | `Select-String -Pattern 'C8RFlZ9f' src/dotnet/QuantEngine.Web/appsettings.json` → 결과 0건 |
|
| 10.1.5 | appsettings.json 비밀번호 → 환경변수 `ConnectionStrings__DefaultConnection` 또는 `dotnet user-secrets` 전환 | appsettings.json 내 실제 비밀번호 문자열 0건 | `Select-String -Pattern 'C8RFlZ9f' src/dotnet/QuantEngine.Web/appsettings.json` → 결과 0건 |
|
||||||
|
|
||||||
**성공 하네스 (데이터 기준)**:
|
**성공 하네스 (데이터 기준)**:
|
||||||
@@ -1431,9 +1432,9 @@ WBS-10.1 (기반 결함 수정)
|
|||||||
| 항목 | 내용 |
|
| 항목 | 내용 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| **작업** | 기존 Domain 계산기 6개에 대한 xUnit 단위 테스트 35건+ 작성. Python golden case JSON을 xUnit `[Theory]` 데이터소스로 활용하는 인프라 구축 |
|
| **작업** | 기존 Domain 계산기 6개에 대한 xUnit 단위 테스트 35건+ 작성. Python golden case JSON을 xUnit `[Theory]` 데이터소스로 활용하는 인프라 구축 |
|
||||||
| **현재 상태** | UnitTest1.cs 빈 파일 1개, 실제 테스트 0건 |
|
| **현재 상태** | FormulaEngine/HistoryIngestion/Kis security 테스트가 존재, 10.2 세부 테스트 확장 중 |
|
||||||
| **담당 파일** | `src/dotnet/QuantEngine.Core.Tests/ExitDecisionsTests.cs`(신규), `KrxTickNormalizerTests.cs`(신규), `ProfitLockCalculatorTests.cs`(신규), `AntiChasingCalculatorTests.cs`(신규), `PullbackTriggerCalculatorTests.cs`(신규), `SellPriceSanityCheckerTests.cs`(신규) |
|
| **담당 파일** | `src/dotnet/QuantEngine.Core.Tests/ExitDecisionsTests.cs`(신규), `KrxTickNormalizerTests.cs`(신규), `ProfitLockCalculatorTests.cs`(신규), `AntiChasingCalculatorTests.cs`(신규), `PullbackTriggerCalculatorTests.cs`(신규), `SellPriceSanityCheckerTests.cs`(신규) |
|
||||||
| **상태** | TODO |
|
| **상태** | 부분 완료 |
|
||||||
|
|
||||||
| 세부 WBS | 작업 | 성공 판단 데이터 | 검증 명령 |
|
| 세부 WBS | 작업 | 성공 판단 데이터 | 검증 명령 |
|
||||||
|----------|------|------------------|----------|
|
|----------|------|------------------|----------|
|
||||||
@@ -1566,9 +1567,9 @@ WBS-10.1 (기반 결함 수정)
|
|||||||
| 항목 | 내용 |
|
| 항목 | 내용 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| **작업** | 빈 Application 프로젝트(Class1.cs)를 실제 서비스 레이어로 전환. Workspace/Approval/Collection/Formula 4개 서비스 구현 |
|
| **작업** | 빈 Application 프로젝트(Class1.cs)를 실제 서비스 레이어로 전환. Workspace/Approval/Collection/Formula 4개 서비스 구현 |
|
||||||
| **현재 상태** | Class1.cs 빈 파일만 존재 |
|
| **현재 상태** | `HistoryIngestionService`, `WorkspaceService`, `ApprovalService`, `CollectionService`, `FormulaService`가 모두 존재하고 `ApplicationServiceTests`로 forward 동작을 검증 중 |
|
||||||
| **담당 파일** | `src/dotnet/QuantEngine.Application/Services/WorkspaceService.cs`(신규), `ApprovalService.cs`(신규), `CollectionService.cs`(신규), `FormulaService.cs`(신규) |
|
| **담당 파일** | `src/dotnet/QuantEngine.Application/Services/WorkspaceService.cs`, `ApprovalService.cs`, `CollectionService.cs`, `FormulaService.cs` |
|
||||||
| **상태** | TODO |
|
| **상태** | 부분 완료 |
|
||||||
|
|
||||||
| 세부 WBS | 작업 | 성공 판단 데이터 |
|
| 세부 WBS | 작업 | 성공 판단 데이터 |
|
||||||
|----------|------|------------------|
|
|----------|------|------------------|
|
||||||
@@ -1579,8 +1580,8 @@ WBS-10.1 (기반 결함 수정)
|
|||||||
|
|
||||||
**성공 하네스 (데이터 기준)**:
|
**성공 하네스 (데이터 기준)**:
|
||||||
```
|
```
|
||||||
검증: dotnet test --filter Service
|
검증: dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj -c Debug --filter ApplicationServiceTests
|
||||||
기대: 13+ tests passed, Class1.cs 삭제됨
|
기대: 4+ tests passed
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -1614,7 +1615,7 @@ WBS-10.1 (기반 결함 수정)
|
|||||||
| 항목 | 내용 |
|
| 항목 | 내용 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| **작업** | 비밀번호 하드코딩 제거, KIS credential 환경변수 강제, read-only guard 우회 방지 테스트, PostgreSQL 스키마 분리 문서화 |
|
| **작업** | 비밀번호 하드코딩 제거, KIS credential 환경변수 강제, read-only guard 우회 방지 테스트, PostgreSQL 스키마 분리 문서화 |
|
||||||
| **현재 상태** | appsettings.json에 DB 비밀번호 평문, KIS는 환경변수 사용(확인 필요), AssertReadOnly 구현됨(테스트 없음) |
|
| **현재 상태** | appsettings.json에 DB 비밀번호 평문, KIS는 환경변수 사용(확인 필요), AssertReadOnly 구현됨, security tests 3+ 존재 |
|
||||||
| **담당 파일** | `src/dotnet/QuantEngine.Web/appsettings.json`, `src/dotnet/QuantEngine.Infrastructure/External/KisApiClient.cs`, `src/dotnet/QuantEngine.Core.Tests/SecurityTests.cs`(신규) |
|
| **담당 파일** | `src/dotnet/QuantEngine.Web/appsettings.json`, `src/dotnet/QuantEngine.Infrastructure/External/KisApiClient.cs`, `src/dotnet/QuantEngine.Core.Tests/SecurityTests.cs`(신규) |
|
||||||
| **상태** | TODO |
|
| **상태** | TODO |
|
||||||
|
|
||||||
@@ -1638,22 +1639,24 @@ WBS-10.1 (기반 결함 수정)
|
|||||||
| 항목 | 내용 |
|
| 항목 | 내용 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| **작업** | Python snapshot_admin_server_v1.py의 편집/조회 기능을 Blazor SSR로 확장. 기본 템플릿 페이지 제거 |
|
| **작업** | Python snapshot_admin_server_v1.py의 편집/조회 기능을 Blazor SSR로 확장. 기본 템플릿 페이지 제거 |
|
||||||
| **현재 상태** | Dashboard.razor에 Settings CRUD 구현, Counter/Weather 기본 페이지 잔존 |
|
| **현재 상태** | `Dashboard.razor`는 데이터 비의존형 상태표시로 단순화되었고, `Operations.razor`가 `Temp/operational_report.json` 고정 렌더 경로를 제공하며, Counter/Weather 기본 페이지는 삭제됨. 공개 배포본은 아직 이전 빌드가 남아 있을 수 있으므로 CI/CD 동기화가 필요함 |
|
||||||
| **담당 파일** | `src/dotnet/QuantEngine.Web/Components/Pages/Dashboard.razor`, `AccountSnapshot.razor`(신규), `CollectionDashboard.razor`(신규) |
|
| **담당 파일** | `src/dotnet/QuantEngine.Web/Components/Pages/Dashboard.razor`, `Operations.razor`(신규), `NavMenu.razor` |
|
||||||
| **상태** | TODO |
|
| **상태** | 부분 완료 |
|
||||||
|
|
||||||
| 세부 WBS | 작업 | 성공 판단 데이터 |
|
| 세부 WBS | 작업 | 성공 판단 데이터 |
|
||||||
|----------|------|------------------|
|
|----------|------|------------------|
|
||||||
| 10.10.1 | Account Snapshot 편집 페이지 — 조회/추가/수정/삭제 CRUD | 4개 CRUD 동작 테스트 PASS |
|
| 10.10.1 | Operational Report 페이지 — `Temp/operational_report.json` 고정 렌더 | 38 sections 인식 + PASS/DATA_MISSING 표시 |
|
||||||
| 10.10.2 | Collection Dashboard — 수집 실행 이력 조회, 에러 로그 표시 | 테이블 조회 + 필터 동작 PASS |
|
| 10.10.2 | Dashboard 상태 페이지 — 데이터 비의존형 요약으로 단순화 | DB 실패 시에도 200 응답 |
|
||||||
| 10.10.3 | Counter.razor / Weather.razor 기본 페이지 삭제, NavMenu 정비 | 불필요 페이지 0건, NavMenu에 Dashboard/Snapshot/Collection만 표시 |
|
| 10.10.3 | Counter.razor / Weather.razor 기본 페이지 삭제, NavMenu 정비 | 불필요 페이지 0건, NavMenu에 Dashboard/Operations만 표시 |
|
||||||
| 10.10.4 | 다크 모드 + 반응형 레이아웃 적용 | 브라우저 렌더링 정상 확인 |
|
| 10.10.4 | 다크 모드 + 반응형 레이아웃 적용 | 브라우저 렌더링 정상 확인 |
|
||||||
|
| 10.10.5 | 배포 동기화 | `snapshot_admin_deploy.yml`가 `/quant/`와 `/quant/operations` 공개 라우트를 배포 후 검증하도록 구성됨 |
|
||||||
|
|
||||||
**성공 하네스 (데이터 기준)**:
|
**성공 하네스 (데이터 기준)**:
|
||||||
```
|
```
|
||||||
검증: dotnet build src/dotnet/QuantEngine.Web/ → 오류 0
|
검증: dotnet build src/dotnet/QuantEngine.Web/ → 오류 0
|
||||||
검증: Counter.razor, Weather.razor 파일 미존재
|
검증: Counter.razor, Weather.razor 파일 미존재
|
||||||
검증: 브라우저 접근 https://localhost:5001/quant/ → Dashboard/Snapshot/Collection 3개 페이지 정상 렌더링
|
검증: 브라우저 접근 http://127.0.0.1:5080/operations → operational_report.json 기반 렌더링
|
||||||
|
검증: 배포 URL http://178.104.200.7/quant/ 에서 `/`와 `/operations`가 200 응답 + 로컬과 동일한 UI 기준을 만족
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -1824,7 +1827,7 @@ WBS-10.1 (기반 결함 수정)
|
|||||||
[x] GAS 라이브러리 강화 (src/gas/core/gas_lib.gs +429줄)
|
[x] GAS 라이브러리 강화 (src/gas/core/gas_lib.gs +429줄)
|
||||||
|
|
||||||
[x] 섹터 리포트 & 대표종목 모니터 고도화
|
[x] 섹터 리포트 & 대표종목 모니터 고도화
|
||||||
etf_representative_monitor.py, render_operational_report.py
|
etf_representative_monitor.py, src/dotnet/QuantEngine.Tools
|
||||||
update_workbook_sector_insights.py (sector_universe_refresh_audit 시트 포함)
|
update_workbook_sector_insights.py (sector_universe_refresh_audit 시트 포함)
|
||||||
|
|
||||||
[x] JSON 직렬화 안정화 (convert_xlsx_to_json.py — datetime/NaN 예외 처리)
|
[x] JSON 직렬화 안정화 (convert_xlsx_to_json.py — datetime/NaN 예외 처리)
|
||||||
@@ -2206,6 +2209,7 @@ python tools/validate_snapshot_admin_web_v1.py
|
|||||||
| P4 GAS thin adapter minimize | `allowed_responsibilities_only=true`, `forbidden_responsibilities_present=false`, `thin_adapter_gate=PASS` | `tools/validate_gas_thin_adapter_v1.py`, `Temp/gas_thin_adapter_validation_v1.json`, `src/gas/core/gas_lib.gs` | `python tools/validate_gas_thin_adapter_v1.py` |
|
| P4 GAS thin adapter minimize | `allowed_responsibilities_only=true`, `forbidden_responsibilities_present=false`, `thin_adapter_gate=PASS` | `tools/validate_gas_thin_adapter_v1.py`, `Temp/gas_thin_adapter_validation_v1.json`, `src/gas/core/gas_lib.gs` | `python tools/validate_gas_thin_adapter_v1.py` |
|
||||||
| P5 PostgreSQL upgrade path | `sqlite_schema_parity=PASS`, `backend_contract_present=true`, `postgres_execution=DATA_GATED`, `caller_compatibility_preserved=true` | `src/quant_engine/data_collection_backend_v1.py`, `src/quant_engine/kis_data_collection_v1.py`, `tests/unit/test_data_collection_store_v1.py`, `tools/generate_postgresql_upgrade_stub_v1.py` | `python -m pytest tests/unit/test_data_collection_store_v1.py -q` |
|
| P5 PostgreSQL upgrade path | `sqlite_schema_parity=PASS`, `backend_contract_present=true`, `postgres_execution=DATA_GATED`, `caller_compatibility_preserved=true` | `src/quant_engine/data_collection_backend_v1.py`, `src/quant_engine/kis_data_collection_v1.py`, `tests/unit/test_data_collection_store_v1.py`, `tools/generate_postgresql_upgrade_stub_v1.py` | `python -m pytest tests/unit/test_data_collection_store_v1.py -q` |
|
||||||
| P6 Snapshot admin web editor | `settings_sheet_web_editor=true`, `account_snapshot_sheet_web_editor=true`, `contenteditable_grid=true`, `api_save_round_trip=PASS`, `kis_collection_dashboard=true`, `workspace_db_is_single_file=true`, `collection_filter_controls=true`, `collection_dashboard_page=true`, `change_timeline_view=true` | `src/quant_engine/snapshot_admin_server_v1.py`, `src/quant_engine/data_collection_store_v1.py`, `src/quant_engine/snapshot_admin_store_v1.py`, `tools/validate_snapshot_admin_web_v1.py`, `tests/unit/test_snapshot_admin_web_v1.py`, `.gitea/workflows/snapshot_admin.yml` | `python tools/validate_snapshot_admin_web_v1.py` |
|
| P6 Snapshot admin web editor | `settings_sheet_web_editor=true`, `account_snapshot_sheet_web_editor=true`, `contenteditable_grid=true`, `api_save_round_trip=PASS`, `kis_collection_dashboard=true`, `workspace_db_is_single_file=true`, `collection_filter_controls=true`, `collection_dashboard_page=true`, `change_timeline_view=true` | `src/quant_engine/snapshot_admin_server_v1.py`, `src/quant_engine/data_collection_store_v1.py`, `src/quant_engine/snapshot_admin_store_v1.py`, `tools/validate_snapshot_admin_web_v1.py`, `tests/unit/test_snapshot_admin_web_v1.py`, `.gitea/workflows/snapshot_admin.yml` | `python tools/validate_snapshot_admin_web_v1.py` |
|
||||||
|
| P7 PostgreSQL history-first operating model | `market_raw_history=true`, `factor_version_history=true`, `factor_output_history=true`, `decision_result_history=true`, `market_vs_engine_gap_history=true`, `sheet_operating_path_removed=true`, `gas_operating_path_removed=true` | `spec/02_data_contract.yaml`, `spec/postgresql_history_contract.yaml`, `docs/DAILY_SIGNAL_TRACKING.md`, `docs/POSTGRESQL_HISTORY_FIRST_OPERATING_MODEL.md` | `python tools/validate_postgresql_history_contract_v1.py` |
|
||||||
| Q1 Qualitative sell pipeline | `mock_api_validation=PASS`, `pipeline_contract=PASS`, `workflow_present=true`, `schedule_present=true`, `package_scripts_present=true` | `.gitea/workflows/qualitative_sell_strategy.yml`, `tools/validate_qualitative_sell_strategy_pipeline_v1.py`, `Temp/qualitative_sell_strategy_pipeline_v1.json` | `python tools/validate_qualitative_sell_strategy_pipeline_v1.py` |
|
| Q1 Qualitative sell pipeline | `mock_api_validation=PASS`, `pipeline_contract=PASS`, `workflow_present=true`, `schedule_present=true`, `package_scripts_present=true` | `.gitea/workflows/qualitative_sell_strategy.yml`, `tools/validate_qualitative_sell_strategy_pipeline_v1.py`, `Temp/qualitative_sell_strategy_pipeline_v1.json` | `python tools/validate_qualitative_sell_strategy_pipeline_v1.py` |
|
||||||
| Q2 Gitea secrets contract | `secrets_contract=PASS`, `workflow_secret_mapping=PASS`, `docs_present=true`, `ci_validation_present=true` | `docs/GITEA_SECRETS_SETUP.md`, `tools/validate_gitea_secrets_contract_v1.py`, `Temp/gitea_secrets_contract_v1.json` | `python tools/validate_gitea_secrets_contract_v1.py` |
|
| Q2 Gitea secrets contract | `secrets_contract=PASS`, `workflow_secret_mapping=PASS`, `docs_present=true`, `ci_validation_present=true` | `docs/GITEA_SECRETS_SETUP.md`, `tools/validate_gitea_secrets_contract_v1.py`, `Temp/gitea_secrets_contract_v1.json` | `python tools/validate_gitea_secrets_contract_v1.py` |
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -13,7 +13,7 @@
|
|||||||
"ops:sell-eval": "python tools/evaluate_qualitative_sell_strategy_accuracy_v1.py --sqlite-db outputs/qualitative_sell_strategy/qualitative_sell_strategy.db",
|
"ops:sell-eval": "python tools/evaluate_qualitative_sell_strategy_accuracy_v1.py --sqlite-db outputs/qualitative_sell_strategy/qualitative_sell_strategy.db",
|
||||||
"ops:sell-validate": "python tools/validate_qualitative_sell_strategy_pipeline_v1.py",
|
"ops:sell-validate": "python tools/validate_qualitative_sell_strategy_pipeline_v1.py",
|
||||||
"ops:postgres-stub": "python tools/generate_postgresql_upgrade_stub_v1.py",
|
"ops:postgres-stub": "python tools/generate_postgresql_upgrade_stub_v1.py",
|
||||||
"ops:render": "python tools/render_operational_report.py --json GatherTradingData.json --output Temp/operational_report.md --report-json-output Temp/operational_report.json",
|
"ops:render": "dotnet run --project src/dotnet/QuantEngine.Tools/QuantEngine.Tools.csproj -- report --packet=Temp/final_decision_packet_active.json --out=Temp/operational_report.json",
|
||||||
"ops:snapshot-web": "python tools/run_snapshot_admin_server_v1.py --reload --db src/quant_engine/snapshot_admin.db --seed GatherTradingData.json",
|
"ops:snapshot-web": "python tools/run_snapshot_admin_server_v1.py --reload --db src/quant_engine/snapshot_admin.db --seed GatherTradingData.json",
|
||||||
"ops:snapshot-web-watch": "python tools/run_snapshot_admin_server_v1.py --reload --db src/quant_engine/snapshot_admin.db --seed GatherTradingData.json",
|
"ops:snapshot-web-watch": "python tools/run_snapshot_admin_server_v1.py --reload --db src/quant_engine/snapshot_admin.db --seed GatherTradingData.json",
|
||||||
"ops:snapshot-validate": "python tools/validate_snapshot_admin_workflow_v1.py",
|
"ops:snapshot-validate": "python tools/validate_snapshot_admin_workflow_v1.py",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"validate-engine-strict": "python tools/run_release_dag_v3.py --mode release --strict",
|
"validate-engine-strict": "python tools/run_release_dag_v3.py --mode release --strict",
|
||||||
"validate-behavioral-coverage": "python tools/validate_behavioral_coverage_v1.py --strict",
|
"validate-behavioral-coverage": "python tools/validate_behavioral_coverage_v1.py --strict",
|
||||||
"validate-engine-integrity": "python tools/run_release_dag_v3.py --mode release --strict",
|
"validate-engine-integrity": "python tools/run_release_dag_v3.py --mode release --strict",
|
||||||
"render-report-json": "python tools/render_operational_report.py --json GatherTradingData.json --output Temp/operational_report.md --report-json-output Temp/operational_report.json"
|
"render-report-json": "dotnet run --project src/dotnet/QuantEngine.Tools/QuantEngine.Tools.csproj -- report --packet=Temp/final_decision_packet_active.json --out=Temp/operational_report.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cheerio": "1.2.0",
|
"cheerio": "1.2.0",
|
||||||
|
|||||||
@@ -172,6 +172,26 @@ quant_feed_contract:
|
|||||||
normalization: "숫자로 읽힌 91160.0, 5930.0 등은 문자열화 후 6자리 zero-pad 적용."
|
normalization: "숫자로 읽힌 91160.0, 5930.0 등은 문자열화 후 6자리 zero-pad 적용."
|
||||||
validation_commands: ["npm run validate-data-sample", "npm run validate-specs"]
|
validation_commands: ["npm run validate-data-sample", "npm run validate-specs"]
|
||||||
xlsx_refresh_rule: "xlsx 원본을 갱신했으면 먼저 DB에 반영한 뒤, 엔진이 DB를 읽어 JSON 파생 보고서를 재생성하고 다시 검증한다."
|
xlsx_refresh_rule: "xlsx 원본을 갱신했으면 먼저 DB에 반영한 뒤, 엔진이 DB를 읽어 JSON 파생 보고서를 재생성하고 다시 검증한다."
|
||||||
|
|
||||||
|
database_first_operating_model:
|
||||||
|
purpose: "운영 이력, 원천 팩터, 파생 최종 팩터, 시장-결과 괴리를 PostgreSQL에 누적해 엔진을 고도화한다."
|
||||||
|
canonical_store:
|
||||||
|
primary: "PostgreSQL"
|
||||||
|
secondary: "SQLite transient cache only"
|
||||||
|
prohibited_operating_path:
|
||||||
|
- "Excel workbook as operational source"
|
||||||
|
- "Google Apps Script as operational source"
|
||||||
|
history_domains:
|
||||||
|
- "market_raw_history"
|
||||||
|
- "factor_version_history"
|
||||||
|
- "factor_output_history"
|
||||||
|
- "decision_result_history"
|
||||||
|
- "market_vs_engine_gap_history"
|
||||||
|
policy:
|
||||||
|
- "최종 팩터와 최종 판단은 DB 이력 테이블에 버전과 시각을 함께 남긴다."
|
||||||
|
- "시장 raw와 엔진 결과의 괴리는 별도 gap history로 적재한다."
|
||||||
|
- "엑셀/시트/Apps Script는 더 이상 운영 경로가 아니라, 역사적 import/export 또는 폐기 대상만 허용한다."
|
||||||
|
- "새 분석·리포트는 PostgreSQL snapshot을 1차 진실원천으로 사용한다."
|
||||||
xlsx_analysis_protocol:
|
xlsx_analysis_protocol:
|
||||||
purpose: "xlsx는 HTS 잔고·거래내역 판독 또는 DB 반영 이전의 보조 감사 소스다. 시장 raw 일반 분석과 최종 보고서 생성은 DB 추적 후의 파생 JSON을 우선한다."
|
purpose: "xlsx는 HTS 잔고·거래내역 판독 또는 DB 반영 이전의 보조 감사 소스다. 시장 raw 일반 분석과 최종 보고서 생성은 DB 추적 후의 파생 JSON을 우선한다."
|
||||||
python_parsing_baseline:
|
python_parsing_baseline:
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
schema_version: "postgresql_history_contract_v1"
|
||||||
|
title: "PostgreSQL History-First Operating Contract"
|
||||||
|
purpose: "시장 원천, 팩터 버전, 최종 팩터 출력, 엔진 의사결정, 시장-엔진 괴리를 PostgreSQL에 누적한다."
|
||||||
|
|
||||||
|
canonical_principles:
|
||||||
|
- "PostgreSQL is the canonical operating history store."
|
||||||
|
- "Excel workbooks and Google Apps Script are not operational sources of truth."
|
||||||
|
- "All derived analysis must be traceable to a versioned DB snapshot."
|
||||||
|
- "Factor outputs and decision outputs must carry provenance and source_version."
|
||||||
|
|
||||||
|
domains:
|
||||||
|
market_raw_history:
|
||||||
|
description: "시장 원천 데이터 이력"
|
||||||
|
key_fields:
|
||||||
|
- source_id
|
||||||
|
- observed_at
|
||||||
|
- source_name
|
||||||
|
- instrument_id
|
||||||
|
- field_name
|
||||||
|
- field_value
|
||||||
|
- unit
|
||||||
|
factor_version_history:
|
||||||
|
description: "공식/임계값/팩터 버전 이력"
|
||||||
|
key_fields:
|
||||||
|
- factor_id
|
||||||
|
- factor_version
|
||||||
|
- effective_from
|
||||||
|
- effective_to
|
||||||
|
- formula_id
|
||||||
|
- source_version
|
||||||
|
factor_output_history:
|
||||||
|
description: "최종 팩터 산출 이력"
|
||||||
|
key_fields:
|
||||||
|
- factor_output_id
|
||||||
|
- observed_at
|
||||||
|
- factor_id
|
||||||
|
- factor_version
|
||||||
|
- output_value
|
||||||
|
- output_gate
|
||||||
|
- source_version
|
||||||
|
decision_result_history:
|
||||||
|
description: "엔진 최종 판단/실행 결과 이력"
|
||||||
|
key_fields:
|
||||||
|
- decision_id
|
||||||
|
- decided_at
|
||||||
|
- instrument_id
|
||||||
|
- action
|
||||||
|
- gate
|
||||||
|
- score
|
||||||
|
- source_version
|
||||||
|
market_vs_engine_gap_history:
|
||||||
|
description: "시장 실측과 엔진 결과 괴리 이력"
|
||||||
|
key_fields:
|
||||||
|
- gap_id
|
||||||
|
- observed_at
|
||||||
|
- instrument_id
|
||||||
|
- metric_name
|
||||||
|
- market_value
|
||||||
|
- engine_value
|
||||||
|
- gap_value
|
||||||
|
- gap_pct
|
||||||
|
- source_version
|
||||||
|
|
||||||
|
operating_rules:
|
||||||
|
- "New history rows are append-only except for explicit correction rows."
|
||||||
|
- "Correction rows must reference corrected_row_id and correction_reason."
|
||||||
|
- "Factor recomputation must preserve previous outputs in history."
|
||||||
|
- "No report should read directly from Excel/GAS when PostgreSQL snapshot is available."
|
||||||
|
|
||||||
|
implementation_targets:
|
||||||
|
- "src/quant_engine/postgresql_history_store_v1.py"
|
||||||
|
- "tools/build_postgresql_history_snapshot_v1.py"
|
||||||
|
- "tools/validate_postgresql_history_contract_v1.py"
|
||||||
|
- "docs/POSTGRESQL_HISTORY_FIRST_OPERATING_MODEL.md"
|
||||||
@@ -12,6 +12,12 @@ purpose: |
|
|||||||
UNVALIDATED → PROVISIONAL → CALIBRATED 상태 전환
|
UNVALIDATED → PROVISIONAL → CALIBRATED 상태 전환
|
||||||
honest_proof_score: 56.57 → 95.0 달성
|
honest_proof_score: 56.57 → 95.0 달성
|
||||||
|
|
||||||
|
implementation_note: |
|
||||||
|
live_outcome_ledger.gs는 Google Sheets 원장 적재/갱신용 GAS thin adapter다.
|
||||||
|
운영 리포트와 검증용 JSON 산출물은 Python 하네스가 Temp/ 경로에 생성한다.
|
||||||
|
GAS는 JSON 리포트를 직접 출력하지 않는다.
|
||||||
|
이후 운영 표준은 PostgreSQL history store이며, 시트/GAS는 운영 경로에서 제외한다.
|
||||||
|
|
||||||
current_state:
|
current_state:
|
||||||
honest_proof_score: 56.57
|
honest_proof_score: 56.57
|
||||||
target_score: 95.0
|
target_score: 95.0
|
||||||
@@ -132,7 +138,8 @@ honest_proof_improvement_path:
|
|||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
tracking_system:
|
tracking_system:
|
||||||
spreadsheet: "live_outcome_ledger (GAS 연동 스프레드시트)"
|
datastore: "PostgreSQL history store"
|
||||||
|
deprecated_surface: "live_outcome_ledger (GAS 연동 스프레드시트)"
|
||||||
|
|
||||||
daily_tasks:
|
daily_tasks:
|
||||||
- "신규 신호 entry 작성 (시작할 때)"
|
- "신규 신호 entry 작성 (시작할 때)"
|
||||||
@@ -151,11 +158,12 @@ tracking_system:
|
|||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
checklist:
|
checklist:
|
||||||
- [ ] "live_outcome_ledger 스프레드시트 생성 (GAS 연동)"
|
- "[ ] live_outcome_ledger 스프레드시트 생성 (GAS 연동)"
|
||||||
- [ ] "신호 기록 템플릿 작성"
|
- "[ ] 신호 기록 템플릿 작성"
|
||||||
- [ ] "T+20 가격 수집 자동화 (GAS)"
|
- "[ ] T+20 가격 수집 자동화 (GAS)"
|
||||||
- [ ] "daily commit: 신호 추가 시마다"
|
- "[ ] Temp/operational_t20_outcome_ledger_v1.json 생성 체인 유지 (Python)"
|
||||||
- [ ] "30개 신호 누적 (약 6주)"
|
- "[ ] daily commit: 신호 추가 시마다"
|
||||||
- [ ] "win_rate >= 60% 달성"
|
- "[ ] 30개 신호 누적 (약 6주)"
|
||||||
- [ ] "CALIBRATED 전환"
|
- "[ ] win_rate >= 60% 달성"
|
||||||
- [ ] "honest_proof_score 95 달성"
|
- "[ ] CALIBRATED 전환"
|
||||||
|
- "[ ] honest_proof_score 95 달성"
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace QuantEngine.Application;
|
|
||||||
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using QuantEngine.Core.Interfaces;
|
||||||
|
using QuantEngine.Core.Models;
|
||||||
|
|
||||||
|
namespace QuantEngine.Application.Services
|
||||||
|
{
|
||||||
|
public class ApprovalService
|
||||||
|
{
|
||||||
|
private readonly IWorkspaceRepository _repository;
|
||||||
|
|
||||||
|
public ApprovalService(IWorkspaceRepository repository)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<WorkspaceApproval>> GetApprovalsAsync() => _repository.GetApprovalsAsync();
|
||||||
|
public Task<WorkspaceApproval?> GetApprovalAsync(string domain, string targetRef) => _repository.GetApprovalAsync(domain, targetRef);
|
||||||
|
public Task<bool> UpsertApprovalAsync(WorkspaceApproval approval) => _repository.UpsertApprovalAsync(approval);
|
||||||
|
|
||||||
|
public Task<IEnumerable<WorkspaceLock>> GetLocksAsync() => _repository.GetLocksAsync();
|
||||||
|
public Task<WorkspaceLock?> GetLockAsync(string domain, string targetRef) => _repository.GetLockAsync(domain, targetRef);
|
||||||
|
public Task<bool> AcquireLockAsync(WorkspaceLock @lock) => _repository.AcquireLockAsync(@lock);
|
||||||
|
public Task<bool> ReleaseLockAsync(string domain, string targetRef) => _repository.ReleaseLockAsync(domain, targetRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using QuantEngine.Core.Interfaces;
|
||||||
|
using QuantEngine.Core.Models;
|
||||||
|
|
||||||
|
namespace QuantEngine.Application.Services
|
||||||
|
{
|
||||||
|
public class CollectionService
|
||||||
|
{
|
||||||
|
private readonly IPostgresqlHistoryStore _historyStore;
|
||||||
|
|
||||||
|
public CollectionService(IPostgresqlHistoryStore historyStore)
|
||||||
|
{
|
||||||
|
_historyStore = historyStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> AppendRunAsync(CollectionRun run)
|
||||||
|
=> _historyStore.AppendAsync("collection_run_history", new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["run_id"] = run.RunId,
|
||||||
|
["collector_name"] = run.CollectorName,
|
||||||
|
["started_at"] = run.StartedAt,
|
||||||
|
["finished_at"] = run.FinishedAt,
|
||||||
|
["status"] = run.Status,
|
||||||
|
["input_source"] = run.InputSource,
|
||||||
|
["output_json_path"] = run.OutputJsonPath,
|
||||||
|
["output_db_path"] = run.OutputDbPath,
|
||||||
|
["notes"] = run.Notes,
|
||||||
|
["created_at"] = run.CreatedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
public Task<int> AppendSnapshotAsync(CollectionSnapshot snapshot)
|
||||||
|
=> _historyStore.AppendAsync("collection_snapshot_history", new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["run_id"] = snapshot.RunId,
|
||||||
|
["dataset_name"] = snapshot.DatasetName,
|
||||||
|
["ticker"] = snapshot.Ticker,
|
||||||
|
["name"] = snapshot.Name,
|
||||||
|
["sector"] = snapshot.Sector,
|
||||||
|
["as_of_date"] = snapshot.AsOfDate,
|
||||||
|
["source_priority"] = snapshot.SourcePriority,
|
||||||
|
["source_status"] = snapshot.SourceStatus,
|
||||||
|
["payload_json"] = snapshot.PayloadJson,
|
||||||
|
["provenance_json"] = snapshot.ProvenanceJson,
|
||||||
|
["created_at"] = snapshot.CreatedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
public Task<int> AppendSourceErrorAsync(CollectionSourceError error)
|
||||||
|
=> _historyStore.AppendAsync("collection_source_error_history", new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["run_id"] = error.RunId,
|
||||||
|
["ticker"] = error.Ticker,
|
||||||
|
["source_name"] = error.SourceName,
|
||||||
|
["error_kind"] = error.ErrorKind,
|
||||||
|
["error_message"] = error.ErrorMessage,
|
||||||
|
["payload_json"] = error.PayloadJson,
|
||||||
|
["created_at"] = error.CreatedAt
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using QuantEngine.Core.Domain;
|
||||||
|
using QuantEngine.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace QuantEngine.Application.Services
|
||||||
|
{
|
||||||
|
public class FormulaService
|
||||||
|
{
|
||||||
|
private readonly IPostgresqlHistoryStore _historyStore;
|
||||||
|
|
||||||
|
public FormulaService(IPostgresqlHistoryStore historyStore)
|
||||||
|
{
|
||||||
|
_historyStore = historyStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimingDecisionResult ComputeTimingDecision(Dictionary<string, object> ctx)
|
||||||
|
=> FormulaEngine.ComputeTimingDecision(ctx);
|
||||||
|
|
||||||
|
public SellDecisionResult ComputeSellDecision(Dictionary<string, object> ctx)
|
||||||
|
=> FormulaEngine.ComputeSellDecision(ctx);
|
||||||
|
|
||||||
|
public FinalDecisionResult ComputeFinalDecision(Dictionary<string, object> ctx)
|
||||||
|
=> FormulaEngine.ComputeFinalDecision(ctx);
|
||||||
|
|
||||||
|
public CashShortfallResult ComputeCashShortfallHarness(
|
||||||
|
Dictionary<string, object> asResult,
|
||||||
|
double totalAsset,
|
||||||
|
Dictionary<string, object> cashFloorInfo,
|
||||||
|
double mrsScore)
|
||||||
|
=> FormulaEngine.ComputeCashShortfallHarness(asResult, totalAsset, cashFloorInfo, mrsScore);
|
||||||
|
|
||||||
|
public CashRecoveryPlanResult ComputeCashRecoveryOptimizer(
|
||||||
|
List<Dictionary<string, object>> sellCandidates,
|
||||||
|
double cashShortfallMinKrw)
|
||||||
|
=> FormulaEngine.ComputeCashRecoveryOptimizer(sellCandidates, cashShortfallMinKrw);
|
||||||
|
|
||||||
|
public Task<int> AppendFormulaRunAsync(string formulaName, Dictionary<string, object?> payload)
|
||||||
|
=> _historyStore.AppendAsync($"formula_{formulaName}_history", payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using QuantEngine.Core.Domain;
|
||||||
|
using QuantEngine.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace QuantEngine.Application.Services
|
||||||
|
{
|
||||||
|
public class HistoryIngestionService
|
||||||
|
{
|
||||||
|
private readonly IPostgresqlHistoryStore _store;
|
||||||
|
|
||||||
|
public HistoryIngestionService(IPostgresqlHistoryStore store)
|
||||||
|
{
|
||||||
|
_store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> AppendDecisionAsync(IDictionary<string, object?> payload)
|
||||||
|
=> _store.AppendAsync("decision_result_history", payload);
|
||||||
|
|
||||||
|
public Task<int> AppendFactorOutputAsync(IDictionary<string, object?> payload)
|
||||||
|
=> _store.AppendAsync("factor_output_history", payload);
|
||||||
|
|
||||||
|
public Task<int> AppendMarketRawAsync(IDictionary<string, object?> payload)
|
||||||
|
=> _store.AppendAsync("market_raw_history", payload);
|
||||||
|
|
||||||
|
public Task<int> AppendGapAsync(IDictionary<string, object?> payload)
|
||||||
|
=> _store.AppendAsync("market_vs_engine_gap_history", payload);
|
||||||
|
|
||||||
|
public Task<int> AppendDecisionAsync(
|
||||||
|
FinalDecisionResult decision,
|
||||||
|
SellDecisionResult? sellDecision = null,
|
||||||
|
TimingDecisionResult? timingDecision = null,
|
||||||
|
string? instrumentId = null,
|
||||||
|
string? sourceVersion = null,
|
||||||
|
string? gate = null)
|
||||||
|
{
|
||||||
|
var payload = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["decision_id"] = Guid.NewGuid().ToString("N"),
|
||||||
|
["decided_at"] = DateTimeOffset.UtcNow,
|
||||||
|
["instrument_id"] = instrumentId ?? string.Empty,
|
||||||
|
["action"] = decision.FinalAction,
|
||||||
|
["gate"] = gate ?? (string.IsNullOrWhiteSpace(sellDecision?.Validation) ? "PASS" : sellDecision.Validation),
|
||||||
|
["score"] = decision.PriorityScore,
|
||||||
|
["source_version"] = sourceVersion ?? decision.DecisionSource,
|
||||||
|
["provenance"] = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["final_action"] = decision.FinalAction,
|
||||||
|
["action_priority"] = decision.ActionPriority,
|
||||||
|
["priority_score"] = decision.PriorityScore,
|
||||||
|
["decision_source"] = decision.DecisionSource,
|
||||||
|
["sell_action"] = sellDecision?.Action,
|
||||||
|
["sell_validation"] = sellDecision?.Validation,
|
||||||
|
["timing_action"] = timingDecision?.Action,
|
||||||
|
["timing_reason"] = timingDecision?.Reason
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return _store.AppendAsync("decision_result_history", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> AppendFactorOutputAsync(
|
||||||
|
string factorId,
|
||||||
|
string factorVersion,
|
||||||
|
double outputValue,
|
||||||
|
string outputGate,
|
||||||
|
string? sourceVersion = null,
|
||||||
|
DateTimeOffset? observedAt = null)
|
||||||
|
{
|
||||||
|
var payload = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["factor_output_id"] = Guid.NewGuid().ToString("N"),
|
||||||
|
["observed_at"] = observedAt ?? DateTimeOffset.UtcNow,
|
||||||
|
["factor_id"] = factorId,
|
||||||
|
["factor_version"] = factorVersion,
|
||||||
|
["output_value"] = outputValue,
|
||||||
|
["output_gate"] = outputGate,
|
||||||
|
["source_version"] = sourceVersion ?? factorVersion,
|
||||||
|
["provenance"] = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["factor_id"] = factorId,
|
||||||
|
["factor_version"] = factorVersion,
|
||||||
|
["output_value"] = outputValue,
|
||||||
|
["output_gate"] = outputGate,
|
||||||
|
["source_version"] = sourceVersion ?? factorVersion
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return _store.AppendAsync("factor_output_history", payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using QuantEngine.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace QuantEngine.Application.Services
|
||||||
|
{
|
||||||
|
public class PostgresqlHistorySnapshotReader : IPostgresqlHistorySnapshotReader
|
||||||
|
{
|
||||||
|
private readonly IPostgresqlHistoryStore _store;
|
||||||
|
|
||||||
|
public PostgresqlHistorySnapshotReader(IPostgresqlHistoryStore store)
|
||||||
|
{
|
||||||
|
_store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IReadOnlyList<IDictionary<string, object?>>> ReadAsync(string domain, int limit = 500)
|
||||||
|
=> _store.SnapshotAsync(domain, limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using QuantEngine.Core.Interfaces;
|
||||||
|
using QuantEngine.Core.Models;
|
||||||
|
|
||||||
|
namespace QuantEngine.Application.Services
|
||||||
|
{
|
||||||
|
public class WorkspaceService
|
||||||
|
{
|
||||||
|
private readonly IWorkspaceRepository _repository;
|
||||||
|
private readonly IPostgresqlHistoryStore _historyStore;
|
||||||
|
|
||||||
|
public WorkspaceService(IWorkspaceRepository repository, IPostgresqlHistoryStore historyStore)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
_historyStore = historyStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<Setting>> GetSettingsAsync() => _repository.GetSettingsAsync();
|
||||||
|
public Task<Setting?> GetSettingByKeyAsync(string key) => _repository.GetSettingByKeyAsync(key);
|
||||||
|
public Task<bool> UpsertSettingAsync(Setting setting) => _repository.UpsertSettingAsync(setting);
|
||||||
|
public Task<bool> DeleteSettingAsync(string key) => _repository.DeleteSettingAsync(key);
|
||||||
|
|
||||||
|
public Task<IEnumerable<AccountSnapshot>> GetAccountSnapshotsAsync() => _repository.GetAccountSnapshotsAsync();
|
||||||
|
public Task<bool> InsertAccountSnapshotsAsync(IEnumerable<AccountSnapshot> snapshots) => _repository.InsertAccountSnapshotsAsync(snapshots);
|
||||||
|
public Task<bool> ClearAccountSnapshotsAsync() => _repository.ClearAccountSnapshotsAsync();
|
||||||
|
|
||||||
|
public Task<int> AppendHistoryAsync(string domain, IDictionary<string, object?> payload) => _historyStore.AppendAsync(domain, payload);
|
||||||
|
public Task<IReadOnlyList<IDictionary<string, object?>>> ReadHistorySnapshotAsync(string domain, int limit = 500) => _historyStore.SnapshotAsync(domain, limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
using QuantEngine.Application.Services;
|
||||||
|
using QuantEngine.Core.Interfaces;
|
||||||
|
using QuantEngine.Core.Models;
|
||||||
|
|
||||||
|
namespace QuantEngine.Core.Tests;
|
||||||
|
|
||||||
|
public class ApplicationServiceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task WorkspaceService_ForwardsSettingAndHistoryOperations()
|
||||||
|
{
|
||||||
|
var repo = new FakeWorkspaceRepository();
|
||||||
|
var history = new FakeHistoryStore();
|
||||||
|
var service = new WorkspaceService(repo, history);
|
||||||
|
|
||||||
|
var setting = new Setting { Ordinal = 1, Key = "risk_mode", ValueJson = "\"RISK_ON\"" };
|
||||||
|
Assert.True(await service.UpsertSettingAsync(setting));
|
||||||
|
Assert.Equal(setting, repo.LastSetting);
|
||||||
|
|
||||||
|
var payload = new Dictionary<string, object?> { ["foo"] = "bar" };
|
||||||
|
Assert.Equal(1, await service.AppendHistoryAsync("decision_result_history", payload));
|
||||||
|
Assert.Equal("decision_result_history", history.LastDomain);
|
||||||
|
Assert.Equal("bar", history.LastPayload?["foo"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApprovalService_ForwardsApprovalAndLockOperations()
|
||||||
|
{
|
||||||
|
var repo = new FakeWorkspaceRepository();
|
||||||
|
var service = new ApprovalService(repo);
|
||||||
|
|
||||||
|
var approval = new WorkspaceApproval { Domain = "settings", TargetRef = "portfolio", Status = "APPROVED" };
|
||||||
|
Assert.True(await service.UpsertApprovalAsync(approval));
|
||||||
|
Assert.Equal(approval, repo.LastApproval);
|
||||||
|
|
||||||
|
var lockRow = new WorkspaceLock { Domain = "settings", TargetRef = "portfolio", LockedBy = "qa", Reason = "review" };
|
||||||
|
Assert.True(await service.AcquireLockAsync(lockRow));
|
||||||
|
Assert.Equal(lockRow, repo.LastLock);
|
||||||
|
Assert.True(await service.ReleaseLockAsync("settings", "portfolio"));
|
||||||
|
Assert.Equal(("settings", "portfolio"), repo.LastReleasedLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CollectionService_AppendsRunSnapshotAndErrorRecords()
|
||||||
|
{
|
||||||
|
var history = new FakeHistoryStore();
|
||||||
|
var service = new CollectionService(history);
|
||||||
|
|
||||||
|
await service.AppendRunAsync(new CollectionRun
|
||||||
|
{
|
||||||
|
RunId = "run-1",
|
||||||
|
CollectorName = "kis",
|
||||||
|
StartedAt = "2026-06-26T09:00:00+09:00",
|
||||||
|
Status = "PASS"
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal("collection_run_history", history.LastDomain);
|
||||||
|
Assert.Equal("run-1", history.LastPayload?["run_id"]);
|
||||||
|
|
||||||
|
await service.AppendSnapshotAsync(new CollectionSnapshot
|
||||||
|
{
|
||||||
|
RunId = "run-1",
|
||||||
|
DatasetName = "decision_result_history",
|
||||||
|
Ticker = "005930",
|
||||||
|
SourcePriority = "KIS",
|
||||||
|
SourceStatus = "PASS",
|
||||||
|
PayloadJson = "{}",
|
||||||
|
ProvenanceJson = "{}"
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal("collection_snapshot_history", history.LastDomain);
|
||||||
|
Assert.Equal("005930", history.LastPayload?["ticker"]);
|
||||||
|
|
||||||
|
await service.AppendSourceErrorAsync(new CollectionSourceError
|
||||||
|
{
|
||||||
|
RunId = "run-1",
|
||||||
|
SourceName = "naver",
|
||||||
|
ErrorKind = "TIMEOUT",
|
||||||
|
ErrorMessage = "timeout"
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal("collection_source_error_history", history.LastDomain);
|
||||||
|
Assert.Equal("TIMEOUT", history.LastPayload?["error_kind"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FormulaService_ForwardsFormulaExecutionAndHistory()
|
||||||
|
{
|
||||||
|
var history = new FakeHistoryStore();
|
||||||
|
var service = new FormulaService(history);
|
||||||
|
|
||||||
|
var timing = service.ComputeTimingDecision(new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["entryModeGate"] = "PASS",
|
||||||
|
["entryMode"] = "BREAKOUT",
|
||||||
|
["leaderGate"] = "PASS",
|
||||||
|
["acGate"] = "CLEAR",
|
||||||
|
["priceStatus"] = "PRICE_OK",
|
||||||
|
["atr20"] = 1.0,
|
||||||
|
["leaderTotal"] = 4,
|
||||||
|
["flowCredit"] = 0.7,
|
||||||
|
["avgTradeValue5D"] = 100,
|
||||||
|
["spreadPct"] = 0.5
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.NotEqual(string.Empty, timing.Action);
|
||||||
|
|
||||||
|
await service.AppendFormulaRunAsync("timing", new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["action"] = timing.Action,
|
||||||
|
["entry_score"] = timing.EntryScore
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal("formula_timing_history", history.LastDomain);
|
||||||
|
Assert.Equal(timing.Action, history.LastPayload?["action"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeWorkspaceRepository : IWorkspaceRepository
|
||||||
|
{
|
||||||
|
public Setting? LastSetting { get; private set; }
|
||||||
|
public WorkspaceApproval? LastApproval { get; private set; }
|
||||||
|
public WorkspaceLock? LastLock { get; private set; }
|
||||||
|
public (string Domain, string TargetRef)? LastReleasedLock { get; private set; }
|
||||||
|
|
||||||
|
public Task<IEnumerable<Setting>> GetSettingsAsync() => Task.FromResult(Enumerable.Empty<Setting>());
|
||||||
|
public Task<Setting?> GetSettingByKeyAsync(string key) => Task.FromResult<Setting?>(null);
|
||||||
|
public Task<bool> UpsertSettingAsync(Setting setting) { LastSetting = setting; return Task.FromResult(true); }
|
||||||
|
public Task<bool> DeleteSettingAsync(string key) => Task.FromResult(true);
|
||||||
|
|
||||||
|
public Task<IEnumerable<AccountSnapshot>> GetAccountSnapshotsAsync() => Task.FromResult(Enumerable.Empty<AccountSnapshot>());
|
||||||
|
public Task<bool> InsertAccountSnapshotsAsync(IEnumerable<AccountSnapshot> snapshots) => Task.FromResult(true);
|
||||||
|
public Task<bool> ClearAccountSnapshotsAsync() => Task.FromResult(true);
|
||||||
|
|
||||||
|
public Task<IEnumerable<WorkspaceApproval>> GetApprovalsAsync() => Task.FromResult(Enumerable.Empty<WorkspaceApproval>());
|
||||||
|
public Task<WorkspaceApproval?> GetApprovalAsync(string domain, string targetRef) => Task.FromResult<WorkspaceApproval?>(null);
|
||||||
|
public Task<bool> UpsertApprovalAsync(WorkspaceApproval approval) { LastApproval = approval; return Task.FromResult(true); }
|
||||||
|
|
||||||
|
public Task<IEnumerable<WorkspaceLock>> GetLocksAsync() => Task.FromResult(Enumerable.Empty<WorkspaceLock>());
|
||||||
|
public Task<WorkspaceLock?> GetLockAsync(string domain, string targetRef) => Task.FromResult<WorkspaceLock?>(null);
|
||||||
|
public Task<bool> AcquireLockAsync(WorkspaceLock @lock) { LastLock = @lock; return Task.FromResult(true); }
|
||||||
|
public Task<bool> ReleaseLockAsync(string domain, string targetRef) { LastReleasedLock = (domain, targetRef); return Task.FromResult(true); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeHistoryStore : IPostgresqlHistoryStore
|
||||||
|
{
|
||||||
|
public string? LastDomain { get; private set; }
|
||||||
|
public IDictionary<string, object?>? LastPayload { get; private set; }
|
||||||
|
|
||||||
|
public Task<int> AppendAsync(string domain, IDictionary<string, object?> payload)
|
||||||
|
{
|
||||||
|
LastDomain = domain;
|
||||||
|
LastPayload = new Dictionary<string, object?>(payload);
|
||||||
|
return Task.FromResult(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IReadOnlyList<IDictionary<string, object?>>> SnapshotAsync(string domain, int limit = 500)
|
||||||
|
=> Task.FromResult<IReadOnlyList<IDictionary<string, object?>>>(Array.Empty<IDictionary<string, object?>>());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using QuantEngine.Core.Domain;
|
||||||
|
|
||||||
|
namespace QuantEngine.Core.Tests;
|
||||||
|
|
||||||
|
public class FormulaEngineTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void TestTimingDecisionNeutral()
|
||||||
|
{
|
||||||
|
var ctx = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "entryModeGate", "PASS" },
|
||||||
|
{ "entryMode", "BREAKOUT" },
|
||||||
|
{ "leaderGate", "PASS" },
|
||||||
|
{ "acGate", "CLEAR" },
|
||||||
|
{ "leaderTotal", 4.0 },
|
||||||
|
{ "flowCredit", 0.8 },
|
||||||
|
{ "ma20Slope", 1.0 },
|
||||||
|
{ "disparity", 0.0 },
|
||||||
|
{ "rsi14", 50.0 },
|
||||||
|
{ "avgTradeValue5D", 100.0 },
|
||||||
|
{ "spreadPct", 0.1 },
|
||||||
|
{ "priceStatus", "PRICE_OK" },
|
||||||
|
{ "atr20", 10.0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = FormulaEngine.ComputeTimingDecision(ctx);
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("BUY_BREAKOUT_PILOT_ONLY", result.Action);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComputeSellDecisionProducesExitTrimWhenRiskWindowIsOpen()
|
||||||
|
{
|
||||||
|
var ctx = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "close", 100.0 },
|
||||||
|
{ "profitPct", 31.0 },
|
||||||
|
{ "tp1Price", 108.0 },
|
||||||
|
{ "tp2Price", 112.0 },
|
||||||
|
{ "timingAction", "BUY_STAGE1_READY" },
|
||||||
|
{ "atr20", 4.0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = FormulaEngine.ComputeSellDecision(ctx);
|
||||||
|
|
||||||
|
Assert.Equal("PROFIT_TRIM_35", result.Action);
|
||||||
|
Assert.Equal(35, result.RatioPct);
|
||||||
|
Assert.Equal("SIGNAL_CONFIRMED", result.Validation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComputeFinalDecisionPromotesSellReadyWhenSellSignalIsConfirmed()
|
||||||
|
{
|
||||||
|
var ctx = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "sellAction", "TRIM_35" },
|
||||||
|
{ "sellValidation", "SIGNAL_CONFIRMED" },
|
||||||
|
{ "timingScoreEntry", 72.0 },
|
||||||
|
{ "timingScoreExit", 15.0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = FormulaEngine.ComputeFinalDecision(ctx);
|
||||||
|
|
||||||
|
Assert.Equal("SELL_READY", result.FinalAction);
|
||||||
|
Assert.Equal(10, result.ActionPriority);
|
||||||
|
Assert.Equal("RULE_ENGINE", result.DecisionSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComputeCashShortfallHarnessCalculatesTargetAndShortfall()
|
||||||
|
{
|
||||||
|
var asResult = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "settlementCashD2Krw", 10_000_000.0 }
|
||||||
|
};
|
||||||
|
var cashFloor = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "minPct", 15.0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = FormulaEngine.ComputeCashShortfallHarness(asResult, 100_000_000.0, cashFloor, 6.0);
|
||||||
|
|
||||||
|
Assert.Equal(10.0, result.CashCurrentPctD2);
|
||||||
|
Assert.Equal(15.0, result.CashTargetPct);
|
||||||
|
Assert.Equal(5_000_000.0, result.CashShortfallMinKrw);
|
||||||
|
Assert.Equal(5_000_000.0, result.CashShortfallTargetKrw);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using QuantEngine.Application.Services;
|
||||||
|
using QuantEngine.Core.Interfaces;
|
||||||
|
|
||||||
|
namespace QuantEngine.Core.Tests;
|
||||||
|
|
||||||
|
public class HistoryIngestionE2ETests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task AppendDecisionThenReadSnapshotRoundTripsThroughApplicationFlow()
|
||||||
|
{
|
||||||
|
var store = new FakeHistoryStore();
|
||||||
|
var ingestion = new HistoryIngestionService(store);
|
||||||
|
var reader = new PostgresqlHistorySnapshotReader(store);
|
||||||
|
|
||||||
|
var appendCount = await ingestion.AppendDecisionAsync(new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["decision_id"] = "dec-001",
|
||||||
|
["decided_at"] = DateTimeOffset.Parse("2026-06-26T09:00:00+09:00"),
|
||||||
|
["instrument_id"] = "005930",
|
||||||
|
["action"] = "BUY",
|
||||||
|
["gate"] = "PASS",
|
||||||
|
["score"] = 87.5,
|
||||||
|
["source_version"] = "v1",
|
||||||
|
["provenance"] = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["source"] = "unit-test"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(1, appendCount);
|
||||||
|
|
||||||
|
var rows = await reader.ReadAsync("decision_result_history", 10);
|
||||||
|
Assert.Single(rows);
|
||||||
|
Assert.Equal("dec-001", rows[0]["decision_id"]);
|
||||||
|
Assert.Equal("005930", rows[0]["instrument_id"]);
|
||||||
|
Assert.Equal("BUY", rows[0]["action"]);
|
||||||
|
Assert.Equal("PASS", rows[0]["gate"]);
|
||||||
|
Assert.Equal(87.5, rows[0]["score"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AppendFactorOutputThenReadSnapshotPreservesPayload()
|
||||||
|
{
|
||||||
|
var store = new FakeHistoryStore();
|
||||||
|
var ingestion = new HistoryIngestionService(store);
|
||||||
|
var reader = new PostgresqlHistorySnapshotReader(store);
|
||||||
|
|
||||||
|
var appendCount = await ingestion.AppendFactorOutputAsync(
|
||||||
|
factorId: "RS_VERDICT_V2",
|
||||||
|
factorVersion: "2026-06-26",
|
||||||
|
outputValue: 1.23,
|
||||||
|
outputGate: "PASS",
|
||||||
|
sourceVersion: "source-42",
|
||||||
|
observedAt: DateTimeOffset.Parse("2026-06-26T10:00:00+09:00"));
|
||||||
|
|
||||||
|
Assert.Equal(1, appendCount);
|
||||||
|
|
||||||
|
var rows = await reader.ReadAsync("factor_output_history", 10);
|
||||||
|
Assert.Single(rows);
|
||||||
|
Assert.Equal("RS_VERDICT_V2", rows[0]["factor_id"]);
|
||||||
|
Assert.Equal("2026-06-26", rows[0]["factor_version"]);
|
||||||
|
Assert.Equal(1.23, rows[0]["output_value"]);
|
||||||
|
Assert.Equal("PASS", rows[0]["output_gate"]);
|
||||||
|
Assert.Equal("source-42", rows[0]["source_version"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeHistoryStore : IPostgresqlHistoryStore
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, List<IDictionary<string, object?>>> _rows = new();
|
||||||
|
|
||||||
|
public Task<int> AppendAsync(string domain, IDictionary<string, object?> payload)
|
||||||
|
{
|
||||||
|
if (!_rows.TryGetValue(domain, out var list))
|
||||||
|
{
|
||||||
|
list = new List<IDictionary<string, object?>>();
|
||||||
|
_rows[domain] = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(new Dictionary<string, object?>(payload));
|
||||||
|
return Task.FromResult(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IReadOnlyList<IDictionary<string, object?>>> SnapshotAsync(string domain, int limit = 500)
|
||||||
|
{
|
||||||
|
if (!_rows.TryGetValue(domain, out var list))
|
||||||
|
{
|
||||||
|
return Task.FromResult<IReadOnlyList<IDictionary<string, object?>>>(Array.Empty<IDictionary<string, object?>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult<IReadOnlyList<IDictionary<string, object?>>>(list.Take(limit).ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using QuantEngine.Infrastructure.Repositories;
|
||||||
|
|
||||||
|
namespace QuantEngine.Core.Tests;
|
||||||
|
|
||||||
|
public class PostgresqlHistoryStoreTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void DomainColumnsExposeCanonicalDomains()
|
||||||
|
{
|
||||||
|
var domains = PostgresqlHistoryStore.GetDomainColumns();
|
||||||
|
|
||||||
|
Assert.Contains("decision_result_history", domains.Keys);
|
||||||
|
Assert.Contains("factor_output_history", domains.Keys);
|
||||||
|
Assert.Contains("market_raw_history", domains.Keys);
|
||||||
|
Assert.Contains("market_vs_engine_gap_history", domains.Keys);
|
||||||
|
Assert.True(domains["decision_result_history"].Contains("decision_id"));
|
||||||
|
Assert.True(domains["factor_output_history"].Contains("output_gate"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildInsertSqlUsesEngineHistoryPrefixAndNamedParameters()
|
||||||
|
{
|
||||||
|
var sql = PostgresqlHistoryStore.BuildInsertSql(
|
||||||
|
"decision_result_history",
|
||||||
|
new[] { "decision_id", "decided_at", "instrument_id", "action", "gate", "score", "source_version", "provenance" });
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
"INSERT INTO engine_history.decision_result_history (decision_id, decided_at, instrument_id, action, gate, score, source_version, provenance) VALUES (@decision_id, @decided_at, @instrument_id, @action, @gate, @score, @source_version, @provenance)",
|
||||||
|
sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildSnapshotSqlUsesCreatedAtDescendingAndLimitParameter()
|
||||||
|
{
|
||||||
|
var sql = PostgresqlHistoryStore.BuildSnapshotSql("factor_output_history", 25);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
"SELECT * FROM engine_history.factor_output_history ORDER BY created_at DESC LIMIT @Limit",
|
||||||
|
sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
@@ -18,4 +18,10 @@
|
|||||||
<Using Include="Xunit" />
|
<Using Include="Xunit" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\QuantEngine.Core\QuantEngine.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\QuantEngine.Application\QuantEngine.Application.csproj" />
|
||||||
|
<ProjectReference Include="..\QuantEngine.Infrastructure\QuantEngine.Infrastructure.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using QuantEngine.Infrastructure.External;
|
||||||
|
|
||||||
|
namespace QuantEngine.Core.Tests;
|
||||||
|
|
||||||
|
public class SecurityTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/uapi/domestic-stock/v1/quotations/inquire-price", "FHKST01010100")]
|
||||||
|
[InlineData("/uapi/domestic-stock/v1/quotations/inquire-investor", "FHKST01010900")]
|
||||||
|
[InlineData("/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice", "FHKST03010100")]
|
||||||
|
public void AssertReadOnly_AllowsReadOnlyQuotationPaths(string path, string trId)
|
||||||
|
{
|
||||||
|
var client = CreateClient();
|
||||||
|
|
||||||
|
var ex = Record.Exception(() => InvokeAssertReadOnly(client, path, trId));
|
||||||
|
|
||||||
|
Assert.Null(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("/uapi/domestic-stock/v1/trading/order-cash", "VTTC0802U")]
|
||||||
|
[InlineData("/uapi/domestic-stock/v1/quotations/inquire-price", "TTTC084000")]
|
||||||
|
[InlineData("/uapi/domestic-stock/v1/trading/order-cash", "FHKST01010100")]
|
||||||
|
public void AssertReadOnly_BlocksTradingPathsOrIds(string path, string trId)
|
||||||
|
{
|
||||||
|
var client = CreateClient();
|
||||||
|
|
||||||
|
var ex = Assert.Throws<TargetInvocationException>(() => InvokeAssertReadOnly(client, path, trId));
|
||||||
|
Assert.IsType<InvalidOperationException>(ex.InnerException);
|
||||||
|
Assert.Contains("BLOCKED", ex.InnerException!.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AssertReadOnly_BlocksKnownTradingTrIdPrefixes()
|
||||||
|
{
|
||||||
|
var client = CreateClient();
|
||||||
|
|
||||||
|
var ex = Assert.Throws<TargetInvocationException>(() => InvokeAssertReadOnly(client, "/uapi/domestic-stock/v1/quotations/inquire-price", "VTTC8434R00"));
|
||||||
|
Assert.IsType<InvalidOperationException>(ex.InnerException);
|
||||||
|
Assert.Contains("TR_ID", ex.InnerException!.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KisApiClient CreateClient()
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("KIS_APP_Key_TEST", "mock-key");
|
||||||
|
Environment.SetEnvironmentVariable("KIS_APP_Secret_TEST", "mock-secret");
|
||||||
|
return new KisApiClient(new HttpClient(new DummyHandler()), new NoopConnectionFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InvokeAssertReadOnly(KisApiClient client, string path, string trId)
|
||||||
|
{
|
||||||
|
var method = typeof(KisApiClient).GetMethod("AssertReadOnly", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||||
|
?? throw new InvalidOperationException("AssertReadOnly method not found.");
|
||||||
|
method.Invoke(client, new object[] { path, trId });
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DummyHandler : HttpMessageHandler
|
||||||
|
{
|
||||||
|
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
|
=> Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK));
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class NoopConnectionFactory : QuantEngine.Infrastructure.Data.IDbConnectionFactory
|
||||||
|
{
|
||||||
|
public System.Data.IDbConnection CreateConnection() => throw new NotSupportedException("Not needed for read-only guard tests.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,85 @@
|
|||||||
namespace QuantEngine.Core.Tests;
|
namespace QuantEngine.Core.Tests;
|
||||||
|
|
||||||
public class UnitTest1
|
public class UnitTest1
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Test1()
|
public void OperationalReportLoader_ParsesCanonicalTempReport()
|
||||||
{
|
{
|
||||||
|
var path = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", "..", "Temp", "operational_report.json"));
|
||||||
|
var report = QuantEngine.Core.Infrastructure.OperationalReportLoader.Load(path);
|
||||||
|
|
||||||
|
Assert.Equal("2026-05-24-operational-report-v1", report.SchemaVersion);
|
||||||
|
Assert.Equal("GatherTradingData.json", report.SourceJson);
|
||||||
|
Assert.Equal(38, report.SectionCount);
|
||||||
|
Assert.True(report.Sections.Count >= 4);
|
||||||
|
Assert.Equal("exec_safety_declaration", report.Sections[0].Name);
|
||||||
|
Assert.Contains("source: .NET operational report builder", report.Sections[0].Preview);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OperationalReportLoader_ReturnsSafeDefaultsWhenFileIsMissing()
|
||||||
|
{
|
||||||
|
var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"), "operational_report.json");
|
||||||
|
var report = QuantEngine.Core.Infrastructure.OperationalReportLoader.Load(path);
|
||||||
|
|
||||||
|
Assert.Equal("n/a", report.SchemaVersion);
|
||||||
|
Assert.Equal("n/a", report.SourceJson);
|
||||||
|
Assert.Equal("n/a", report.GeneratedAt);
|
||||||
|
Assert.Equal(0, report.SectionCount);
|
||||||
|
Assert.Empty(report.Sections);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OperationalReportLoader_UsesSectionCountFromPayloadWhenPresent()
|
||||||
|
{
|
||||||
|
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
var path = Path.Combine(tempDir, "operational_report.json");
|
||||||
|
|
||||||
|
File.WriteAllText(path, """
|
||||||
|
{
|
||||||
|
"schema_version": "test-schema",
|
||||||
|
"source_json": "fixture.json",
|
||||||
|
"generated_at": "2026-06-26T00:00:00+00:00",
|
||||||
|
"section_count": 2,
|
||||||
|
"sections": [
|
||||||
|
{ "name": "alpha", "title": "Alpha", "markdown": "alpha body" },
|
||||||
|
{ "name": "beta", "title": "Beta", "markdown": "beta body" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
var report = QuantEngine.Core.Infrastructure.OperationalReportLoader.Load(path);
|
||||||
|
|
||||||
|
Assert.Equal("test-schema", report.SchemaVersion);
|
||||||
|
Assert.Equal("fixture.json", report.SourceJson);
|
||||||
|
Assert.Equal(2, report.SectionCount);
|
||||||
|
Assert.Equal(2, report.Sections.Count);
|
||||||
|
Assert.Equal("alpha", report.Sections[0].Name);
|
||||||
|
Assert.Equal("alpha body", report.Sections[0].Preview);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OperationalReportLoader_PreservesEmptySectionsWithoutThrowing()
|
||||||
|
{
|
||||||
|
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
var path = Path.Combine(tempDir, "operational_report.json");
|
||||||
|
|
||||||
|
File.WriteAllText(path, """
|
||||||
|
{
|
||||||
|
"schema_version": "empty-schema",
|
||||||
|
"source_json": "fixture.json",
|
||||||
|
"generated_at": "2026-06-26T00:00:00+00:00",
|
||||||
|
"section_count": 0,
|
||||||
|
"sections": []
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
var report = QuantEngine.Core.Infrastructure.OperationalReportLoader.Load(path);
|
||||||
|
|
||||||
|
Assert.Equal(0, report.SectionCount);
|
||||||
|
Assert.Empty(report.Sections);
|
||||||
|
Assert.Equal("empty-schema", report.SchemaVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,433 @@
|
|||||||
|
{
|
||||||
|
"runtimeTarget": {
|
||||||
|
"name": ".NETCoreApp,Version=v10.0",
|
||||||
|
"signature": ""
|
||||||
|
},
|
||||||
|
"compilationOptions": {},
|
||||||
|
"targets": {
|
||||||
|
".NETCoreApp,Version=v10.0": {
|
||||||
|
"QuantEngine.Core.Tests/1.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.NET.Test.Sdk": "17.14.1",
|
||||||
|
"QuantEngine.Core": "1.0.0",
|
||||||
|
"xunit": "2.9.3"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"QuantEngine.Core.Tests.dll": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.CodeCoverage/17.14.1": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.VisualStudio.CodeCoverage.Shim.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.225.12603"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.NET.Test.Sdk/17.14.1": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.CodeCoverage": "17.14.1",
|
||||||
|
"Microsoft.TestPlatform.TestHost": "17.14.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.TestPlatform.ObjectModel/17.14.1": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.TestPlatform.CoreUtilities.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Microsoft.TestPlatform.PlatformAbstractions.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"lib/net8.0/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net8.0/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net8.0/de/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net8.0/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net8.0/es/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net8.0/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net8.0/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/it/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net8.0/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net8.0/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.TestPlatform.TestHost/17.14.1": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.TestPlatform.ObjectModel": "17.14.1",
|
||||||
|
"Newtonsoft.Json": "13.0.3"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.TestPlatform.CommunicationUtilities.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Microsoft.TestPlatform.CrossPlatEngine.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Microsoft.TestPlatform.Utilities.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Microsoft.VisualStudio.TestPlatform.Common.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
},
|
||||||
|
"lib/net8.0/testhost.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"lib/net8.0/cs/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net8.0/cs/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net8.0/cs/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net8.0/de/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net8.0/de/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net8.0/de/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net8.0/es/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net8.0/es/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net8.0/es/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net8.0/fr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/fr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/fr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/it/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net8.0/it/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net8.0/it/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ja/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ja/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ja/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ko/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ko/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ko/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pl/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pl/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pl/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pt-BR/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pt-BR/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pt-BR/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ru/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ru/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ru/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net8.0/tr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/tr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/tr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hans/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hans/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hant/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hant/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Newtonsoft.Json/13.0.3": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/Newtonsoft.Json.dll": {
|
||||||
|
"assemblyVersion": "13.0.0.0",
|
||||||
|
"fileVersion": "13.0.3.27908"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xunit/2.9.3": {
|
||||||
|
"dependencies": {
|
||||||
|
"xunit.assert": "2.9.3",
|
||||||
|
"xunit.core": "2.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xunit.abstractions/2.0.3": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/netstandard2.0/xunit.abstractions.dll": {
|
||||||
|
"assemblyVersion": "2.0.0.0",
|
||||||
|
"fileVersion": "2.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xunit.assert/2.9.3": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/xunit.assert.dll": {
|
||||||
|
"assemblyVersion": "2.9.3.0",
|
||||||
|
"fileVersion": "2.9.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xunit.core/2.9.3": {
|
||||||
|
"dependencies": {
|
||||||
|
"xunit.extensibility.core": "2.9.3",
|
||||||
|
"xunit.extensibility.execution": "2.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xunit.extensibility.core/2.9.3": {
|
||||||
|
"dependencies": {
|
||||||
|
"xunit.abstractions": "2.0.3"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/netstandard1.1/xunit.core.dll": {
|
||||||
|
"assemblyVersion": "2.9.3.0",
|
||||||
|
"fileVersion": "2.9.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xunit.extensibility.execution/2.9.3": {
|
||||||
|
"dependencies": {
|
||||||
|
"xunit.extensibility.core": "2.9.3"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/netstandard1.1/xunit.execution.dotnet.dll": {
|
||||||
|
"assemblyVersion": "2.9.3.0",
|
||||||
|
"fileVersion": "2.9.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"QuantEngine.Core/1.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"QuantEngine.Core.dll": {
|
||||||
|
"assemblyVersion": "1.0.0.0",
|
||||||
|
"fileVersion": "1.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"QuantEngine.Core.Tests/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"serviceable": false,
|
||||||
|
"sha512": ""
|
||||||
|
},
|
||||||
|
"Microsoft.CodeCoverage/17.14.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg==",
|
||||||
|
"path": "microsoft.codecoverage/17.14.1",
|
||||||
|
"hashPath": "microsoft.codecoverage.17.14.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.NET.Test.Sdk/17.14.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==",
|
||||||
|
"path": "microsoft.net.test.sdk/17.14.1",
|
||||||
|
"hashPath": "microsoft.net.test.sdk.17.14.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.TestPlatform.ObjectModel/17.14.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==",
|
||||||
|
"path": "microsoft.testplatform.objectmodel/17.14.1",
|
||||||
|
"hashPath": "microsoft.testplatform.objectmodel.17.14.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.TestPlatform.TestHost/17.14.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==",
|
||||||
|
"path": "microsoft.testplatform.testhost/17.14.1",
|
||||||
|
"hashPath": "microsoft.testplatform.testhost.17.14.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Newtonsoft.Json/13.0.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
|
||||||
|
"path": "newtonsoft.json/13.0.3",
|
||||||
|
"hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"xunit/2.9.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
|
||||||
|
"path": "xunit/2.9.3",
|
||||||
|
"hashPath": "xunit.2.9.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"xunit.abstractions/2.0.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==",
|
||||||
|
"path": "xunit.abstractions/2.0.3",
|
||||||
|
"hashPath": "xunit.abstractions.2.0.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"xunit.assert/2.9.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==",
|
||||||
|
"path": "xunit.assert/2.9.3",
|
||||||
|
"hashPath": "xunit.assert.2.9.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"xunit.core/2.9.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
|
||||||
|
"path": "xunit.core/2.9.3",
|
||||||
|
"hashPath": "xunit.core.2.9.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"xunit.extensibility.core/2.9.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
|
||||||
|
"path": "xunit.extensibility.core/2.9.3",
|
||||||
|
"hashPath": "xunit.extensibility.core.2.9.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"xunit.extensibility.execution/2.9.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
|
||||||
|
"path": "xunit.extensibility.execution/2.9.3",
|
||||||
|
"hashPath": "xunit.extensibility.execution.2.9.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"QuantEngine.Core/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"serviceable": false,
|
||||||
|
"sha512": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"runtimeOptions": {
|
||||||
|
"tfm": "net10.0",
|
||||||
|
"framework": {
|
||||||
|
"name": "Microsoft.NETCore.App",
|
||||||
|
"version": "10.0.0"
|
||||||
|
},
|
||||||
|
"configProperties": {
|
||||||
|
"MSTest.EnableParentProcessQuery": true,
|
||||||
|
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user