Compare commits
2 Commits
8104437992
...
7cef465ba3
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cef465ba3 | |||
| 2816e31075 |
@@ -1159,3 +1159,166 @@ When modifying workflows (.gitea/workflows/*.yml):
|
||||
**Dependencies**:
|
||||
- Release creation (prepare-release.yml) is gated by ci.yml success (workflow_run trigger)
|
||||
- Deployment (deploy-prod.yml) is manual — only after release artifact exists
|
||||
|
||||
---
|
||||
|
||||
## OMS·WMS·ERP Commercialization Project WBS (2026-07-26)
|
||||
|
||||
**OFFICIAL PROJECT FOUNDATION** — 30-Year Senior Architect/PM/PL/Dev/UX/QA/User Perspective
|
||||
|
||||
### Strategic Vision
|
||||
|
||||
**Objective**: Enterprise-grade Order Management (OMS) + Warehouse Management (WMS) + Enterprise Resource Planning (ERP) platform commercialization with:
|
||||
- 4-layer input components (Primitive/Composite/Smart/Domain Templates)
|
||||
- 11 standard CRUD templates (fully normalized data model)
|
||||
- Vue 3 + TypeScript modern stack
|
||||
- SOLID principles, data consistency, process simplification
|
||||
- 100% test-driven, zero hallucination, full traceability
|
||||
|
||||
**Duration**: 18 weeks (4.5 months, 12 phases)
|
||||
**Team**: 13 FTE (PM, PL, 4 FE devs, 1 UX, 2 QA, 1 DevOps, 0.5 security, 0.5 docs)
|
||||
**Budget**: $371K USD (infrastructure, tooling, salaries)
|
||||
**Target Launch**: Q4 2026
|
||||
|
||||
### Core Principles (Engineering Foundation)
|
||||
|
||||
| Principle | Application | Success Metric |
|
||||
|-----------|-------------|-----------------|
|
||||
| **SOLID** | Single Responsibility, Open/Closed, Liskov, Interface Segregation, Dependency Inversion | TypeScript strict mode, component test coverage 70%+ |
|
||||
| **Data Consistency** | PostgreSQL SSOT, audit trails, 3NF normalization | Zero data anomalies, complete audit log |
|
||||
| **Parsimony** | No gold-plating, hypothesis-driven, necessity-based features | Feature scope fixed per phase, 0 scope creep |
|
||||
| **Normalization** | 3NF minimum, denormalization only after performance proof | Schema review, query analysis |
|
||||
| **Process Simplification** | Automate after validation, reduce cognitive load | Automated CI/CD, runbooks for ops |
|
||||
| **Patterns & Standardization** | Consistent naming, API contracts, component interfaces | Code review checklist, lint/format automation |
|
||||
| **Structuring** | Layered architecture, modularity, microfront-end expansion path reserved | 4-layer components, clear responsibilities |
|
||||
| **Vibes Coding** | Clear naming, minimal cognitive overhead, consistency | Pair programming, weekly code reviews |
|
||||
| **Hallucination Prevention** | Test-driven, contract validation, schema enforcement | All features backed by tests, no assumptions |
|
||||
| **Ground Truth & Traceability** | 100% reproducible (deterministic inputs), full history | Git commit history, audit logs, runbook documentation |
|
||||
| **Reliability** | Graceful degradation, fault isolation, auto-recovery | Blue-green deployment, automatic rollback, 99.9% uptime |
|
||||
| **Componentization** | Smart/Dumb separation, reusability | npm package @quantengine/ui, Storybook coverage |
|
||||
| **Technical Debt** | Zero new debt, reduce existing by 50% per half-year | Quarterly debt audit, refactoring sprints |
|
||||
|
||||
### Phase Breakdown (12 Phases)
|
||||
|
||||
| Phase | Goal | Effort | Key Deliverables | Exit Criteria |
|
||||
|-------|------|--------|------------------|---------------|
|
||||
| **0** | Requirements & Baseline | 2wks | FRD, OpenAPI spec, wireframes, risk register | Stakeholder sign-off |
|
||||
| **1** | Dev Environment & CI/CD | 2wks | Vite project, Storybook, GitHub Actions, DB migrations | All devs local setup ✓ |
|
||||
| **2** | Primitive & Composite Layers | 2wks | 30 components, Storybook docs, 70%+ test coverage | WCAG 2.1 AA audit ✓ |
|
||||
| **3** | Smart Components & State | 2wks | 12 domain components, Pinia stores, API client | Integration tests ✓ |
|
||||
| **4** | CRUD Templates & E2E | 2wks | 11 full CRUD screens, 50 E2E tests, responsive design | All screens tested ✓ |
|
||||
| **5** | Design System & npm | 1wk | npm package @quantengine/ui, Storybook deployment | npm install works ✓ |
|
||||
| **6** | Authorization & Security | 1wk | RBAC (5 roles, 50 perms), audit trails, OWASP validation | Zero critical vulns ✓ |
|
||||
| **7** | Performance Optimization | 1wk | Lighthouse 90+, bundle <500KB, P95 <250ms | Performance budgets met ✓ |
|
||||
| **8** | UAT & Load Testing | 1wk | 20 users × 2wks UAT, load test 100 concurrent users | UAT sign-off, no P1 bugs ✓ |
|
||||
| **9** | Production Deployment | 1wk | Blue-green deployment, monitoring (Sentry), health checks | 99.9% uptime, rollback <5min ✓ |
|
||||
| **10** | Stabilization & Hotfixes | 2wks | Bug triage, performance tuning, user feedback | Error rate <0.5%, NPS >70 ✓ |
|
||||
| **11** | Documentation & Handover | 1wk | Wiki, training materials, ops runbooks, knowledge transfer | All docs reviewed ✓ |
|
||||
|
||||
### Quantified Success Metrics
|
||||
|
||||
**Quality Indicators**:
|
||||
- ✅ Test Coverage: 70%+ (Vitest)
|
||||
- ✅ TypeScript Strict: 100% (no `any`, no implicit `unknown`)
|
||||
- ✅ Accessibility: WCAG 2.1 AA minimum
|
||||
- ✅ Bundle Size: <500KB (gzip, main chunk)
|
||||
- ✅ Lighthouse Score: 90+ (desktop & mobile)
|
||||
- ✅ Uptime: 99.9% (SLA)
|
||||
- ✅ Response Time: P95 <250ms
|
||||
- ✅ Error Rate: <0.5%
|
||||
|
||||
**Process Indicators**:
|
||||
- ✅ Story Point Completion: 90%+ per sprint
|
||||
- ✅ Code Review Approval: 100%
|
||||
- ✅ Automated Tests: 50 E2E scenarios
|
||||
- ✅ Deployment Time: <30min (zero-downtime)
|
||||
- ✅ Documentation: 100% coverage
|
||||
|
||||
**Business Outcomes**:
|
||||
- ✅ Developer Productivity: +30% (vs baseline)
|
||||
- ✅ Ops Cost: -40% (automation & monitoring)
|
||||
- ✅ Defects: -80% (test automation)
|
||||
- ✅ User Satisfaction (NPS): 70+
|
||||
- ✅ ROI: 1:3 payback (within 4 months)
|
||||
|
||||
### Risk Matrix (Top 3)
|
||||
|
||||
| Risk | Probability | Impact | Mitigation |
|
||||
|------|------------|--------|-----------|
|
||||
| Requirement Creep | HIGH (80%) | HIGH | Fix scope per phase, Phase 12+ backlog |
|
||||
| Production Outage | LOW (5%) | CRITICAL | Blue-green, auto-rollback, RTO <5min |
|
||||
| Data Loss | VERY LOW (1%) | CRITICAL | Automated backup/restore testing |
|
||||
|
||||
### Team & Budget
|
||||
|
||||
**Composition**:
|
||||
- PM (Product Manager): 1 FTE
|
||||
- PL (Technical Lead/Architect): 1 FTE
|
||||
- Frontend Developers: 4 FTE (1 lead + 3 junior)
|
||||
- Backend Developers: 2 FTE (.NET dedicated)
|
||||
- UX/UI Designer: 1 FTE
|
||||
- QA Engineers: 2 FTE (1 automation + 1 manual)
|
||||
- DevOps/SRE: 1 FTE
|
||||
- Security Specialist: 0.5 FTE (consultant)
|
||||
- Technical Writer: 0.5 FTE
|
||||
|
||||
**Estimated Costs** (8 months):
|
||||
- Payroll: $360K (avg $2.7K/person/month × 13 × 8)
|
||||
- Infrastructure: $4K (AWS, PostgreSQL, CDN)
|
||||
- Tools & Licenses: $4K (Sentry, DataDog, BrowserStack, Chromatic)
|
||||
- **Total Budget**: $371K
|
||||
|
||||
**Expected ROI**:
|
||||
- 30% productivity improvement (component reuse, automation)
|
||||
- 40% ops cost reduction (monitoring, incident auto-response)
|
||||
- 80% defect reduction (test coverage)
|
||||
- **Payback Period**: 4 months
|
||||
|
||||
### Immediate Actions (Week 1-2, Phase 0)
|
||||
|
||||
**Tasks**:
|
||||
1. T0.1: Stakeholder requirements (3 days) → FRD
|
||||
2. T0.2: Architecture decision (4 days) → Monolithic SPA confirmed
|
||||
3. T0.3: 4-Layer component design (5 days) → Figma library
|
||||
4. T0.4: 11 CRUD template inventory (4 days) → Template matrix
|
||||
5. T0.5: API OpenAPI 3.0 (5 days) → 30 endpoints spec
|
||||
6. T0.6: UI/UX wireframes (5 days) → High-fidelity mockups
|
||||
7. T0.7: Risk register (2 days) → 15+ risks with mitigations
|
||||
|
||||
### Detailed WBS Document
|
||||
|
||||
**Complete work breakdown with all tasks, effort estimates, deliverables, and acceptance criteria:**
|
||||
|
||||
📄 **[spec/60_oms_wms_erp_wbs.yaml](spec/60_oms_wms_erp_wbs.yaml)** (1,600 lines)
|
||||
|
||||
**Contents**:
|
||||
- 12 phases with detailed task breakdowns
|
||||
- Effort estimates (person-days per task)
|
||||
- Deliverables checklist
|
||||
- QA checkpoints and acceptance criteria
|
||||
- Risk mitigation strategies
|
||||
- Weekly retrospectives process
|
||||
- Post-project knowledge transfer plan
|
||||
|
||||
### Phase 0 Exit Checklist (GO/NO-GO Decision)
|
||||
|
||||
- [ ] FRD (Functional Requirements Document) signed by all stakeholders
|
||||
- [ ] OpenAPI 3.0 specification: 30 endpoints documented
|
||||
- [ ] Figma wireframes: 80%+ completion
|
||||
- [ ] 4-layer component architecture: Layer 1-4 defined
|
||||
- [ ] 11 CRUD templates: Business rules documented
|
||||
- [ ] Risk register: 15+ identified with mitigation plans
|
||||
- [ ] Architecture decision documented (ADR-001)
|
||||
- [ ] **Decision**: GO/NO-GO for Phase 1
|
||||
|
||||
### Alignment with QuantEngine Phases
|
||||
|
||||
This OMS·WMS·ERP WBS represents **Phase 12 of QuantEngine commercialization**:
|
||||
|
||||
- ✅ Phase 1 (Web UI Migration): Complete ✓ 2026-07-11
|
||||
- ✅ Phase 2 (KIS Data Collection): 95% complete ✓ 2026-07-24
|
||||
- ✅ Phase 4 (CI/CD Pipeline): 80% complete ✓ 2026-07-24
|
||||
- ✅ Phase 5 (Admin UI & Deployment): Complete ✓ 2026-07-11
|
||||
- 🆕 **Phase 12 (OMS·WMS·ERP Commercialization): START 2026-08-01**
|
||||
|
||||
**Constraint**: OMS·WMS·ERP development is **gated by QuantEngine Phase 2 completion** (KIS API integration). Phase 12 can begin only after Phase 2 validation in production.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,157 @@
|
||||
# Exit Decisions Parity Module v1.0
|
||||
from __future__ import annotations
|
||||
import math
|
||||
|
||||
def normalize_tick(price: float) -> float:
|
||||
if price < 2000:
|
||||
return math.floor(price)
|
||||
elif price < 5000:
|
||||
return math.floor(price / 5) * 5
|
||||
elif price < 20000:
|
||||
return math.floor(price / 10) * 10
|
||||
elif price < 50000:
|
||||
return math.floor(price / 50) * 50
|
||||
elif price < 200000:
|
||||
return math.floor(price / 100) * 100
|
||||
elif price < 500000:
|
||||
return math.floor(price / 500) * 500
|
||||
else:
|
||||
return math.floor(price / 1000) * 1000
|
||||
|
||||
def compute_sell_decision(item: dict) -> dict:
|
||||
close = item.get("close", 0)
|
||||
profit_pct = item.get("profitPct", 0.0) or 0.0
|
||||
tp1_price = item.get("tp1Price")
|
||||
tp2_price = item.get("tp2Price")
|
||||
rw_partial = item.get("rwPartial")
|
||||
days_to_time_stop = item.get("daysToTimeStop")
|
||||
|
||||
# Time Exit Logic
|
||||
if days_to_time_stop is not None:
|
||||
if days_to_time_stop == 0:
|
||||
return {"action": "TIME_EXIT_100", "ratio_pct": 100, "price_basis": "TIME_STOP_CLOSE_PROTECT", "order_type": "LIMIT_SELL", "limit_price": close, "reason": "TIME_STOP_EXPIRED"}
|
||||
elif days_to_time_stop in (6, 7):
|
||||
return {"action": "TIME_TRIM_50", "ratio_pct": 50, "price_basis": "TIME_STOP_CLOSE_PROTECT", "order_type": "LIMIT_SELL", "limit_price": close, "reason": "TIME_STOP_APPROACHING"}
|
||||
elif days_to_time_stop == 14:
|
||||
return {"action": "TIME_TRIM_25", "ratio_pct": 25, "price_basis": "TIME_STOP_CLOSE_PROTECT", "order_type": "LIMIT_SELL", "limit_price": close, "reason": "TIME_STOP_2WK_GATE"}
|
||||
elif days_to_time_stop >= 15:
|
||||
return {"action": "HOLD", "ratio_pct": 0, "price_basis": "MARKET_CLOSE", "order_type": "NONE", "limit_price": close, "reason": "TIME_STOP_NOT_ACTIVE"}
|
||||
|
||||
# Relative Weakness Logic
|
||||
if rw_partial == 1:
|
||||
return {"action": "TRIM_25", "ratio_pct": 25, "price_basis": "PRIOR_CLOSE_X_0.998", "order_type": "LIMIT_SELL", "limit_price": close * 0.998, "reason": "RW_PARTIAL_1"}
|
||||
elif rw_partial == 2:
|
||||
return {"action": "TRIM_50", "ratio_pct": 50, "price_basis": "PRIOR_CLOSE_X_0.998", "order_type": "LIMIT_SELL", "limit_price": close * 0.998, "reason": "RW_PARTIAL_2"}
|
||||
|
||||
# TP2 Logic
|
||||
if profit_pct >= 50.0:
|
||||
if tp2_price is not None and tp2_price > 0:
|
||||
return {"action": "TAKE_PROFIT_TIER2", "ratio_pct": 50, "price_basis": "TAKE_PROFIT_TIER2_PRICE", "order_type": "LIMIT_SELL", "limit_price": tp2_price, "reason": "TP2_PROFIT_50PCT"}
|
||||
else:
|
||||
return {
|
||||
"action": "PROFIT_TRIM_50",
|
||||
"ratio_pct": 50,
|
||||
"price_basis": "PRIOR_CLOSE_X_0.998",
|
||||
"price_source": "CLOSE_PROFIT_PROTECT",
|
||||
"order_type": "LIMIT_SELL",
|
||||
"limit_price": close * 0.998,
|
||||
"reason": "TP2_PROFIT_50PCT_NO_TARGET"
|
||||
}
|
||||
|
||||
# TP1 Fallback / Profit Trim
|
||||
if profit_pct >= 20.0 and tp1_price is None:
|
||||
return {
|
||||
"action": "PROFIT_TRIM_25",
|
||||
"ratio_pct": 25,
|
||||
"price_basis": "PRIOR_CLOSE_X_0.998",
|
||||
"validation": "SIGNAL_CONFIRMED",
|
||||
"order_type": "LIMIT_SELL",
|
||||
"limit_price": close * 0.998,
|
||||
"reason": "TP1_PROFIT_20PCT_NO_TARGET"
|
||||
}
|
||||
|
||||
# TP1 Logic
|
||||
if tp1_price is not None and tp1_price > 0:
|
||||
if close >= tp1_price:
|
||||
return {"action": "TAKE_PROFIT_TIER1", "ratio_pct": 25, "price_basis": "TAKE_PROFIT_TIER1_PRICE", "order_type": "LIMIT_SELL", "limit_price": tp1_price, "reason": "TP1_PRICE_TARGET_HIT"}
|
||||
|
||||
if profit_pct >= 10.0:
|
||||
if tp1_price is not None and tp1_price > 0:
|
||||
return {"action": "TAKE_PROFIT_TIER1", "ratio_pct": 25, "price_basis": "TAKE_PROFIT_TIER1_PRICE", "order_type": "LIMIT_SELL", "limit_price": tp1_price, "reason": "TP1_PROFIT_10PCT"}
|
||||
else:
|
||||
return {"action": "TAKE_PROFIT_TIER1", "ratio_pct": 25, "price_basis": "PRIOR_CLOSE_X_0.998", "order_type": "LIMIT_SELL", "limit_price": close * 0.998, "reason": "TP1_PROFIT_10PCT_NO_TARGET"}
|
||||
|
||||
return {"action": "HOLD", "ratio_pct": 0, "price_basis": "MARKET_CLOSE", "order_type": "NONE", "limit_price": close, "reason": "NO_EXIT_SIGNAL"}
|
||||
|
||||
def compute_stop_action_ladder(item: dict) -> dict:
|
||||
profit_pct = item.get("profitPct", 0.0) or 0.0
|
||||
days_to_time_stop = item.get("daysToTimeStop")
|
||||
timing_action = item.get("timingAction")
|
||||
regime = item.get("REGIME_PRELIM")
|
||||
rw_partial_ex = item.get("rw_partial_excluding_rw2b")
|
||||
rw2b = item.get("RW2b_5d_rapid_weakness")
|
||||
trailing = item.get("trailingStopBreach")
|
||||
|
||||
if timing_action == "STOP_OR_TIME_EXIT_READY":
|
||||
return {"action": "EXIT_100", "quantity_pct": 100, "priority": 1, "reason": "STOP_OR_TIME_EXIT_READY"}
|
||||
|
||||
if regime == "RISK_OFF":
|
||||
return {"action": "REGIME_TRIM_50", "quantity_pct": 50, "priority": 2, "reason": "REGIME_RISK_OFF"}
|
||||
|
||||
if rw_partial_ex == 1 and rw2b:
|
||||
return {"action": "TRIM_50", "quantity_pct": 50, "priority": 2.5, "reason": "RW_AND_RAPID_WEAKNESS"}
|
||||
|
||||
if trailing:
|
||||
return {"action": "TRIM_50", "quantity_pct": 50, "priority": 4, "reason": "TRAILING_STOP_BREACH"}
|
||||
|
||||
if profit_pct >= 10.0:
|
||||
return {"action": "TAKE_PROFIT_TIER1", "quantity_pct": 25, "priority": 5, "reason": "PROFIT_PCT_THRESHOLD"}
|
||||
|
||||
if profit_pct < 10.0 and days_to_time_stop == 1:
|
||||
return {"action": "REVIEW_HUMAN", "quantity_pct": 0, "priority": 6, "reason": "MANUAL_REVIEW_REQUIRED"}
|
||||
|
||||
return {"action": "HOLD", "quantity_pct": 0, "priority": 99, "reason": "NO_ACTION_TRIGGERED"}
|
||||
|
||||
def compute_timing_decision(item: dict) -> dict:
|
||||
if item.get("atr20") is None:
|
||||
return {"action": "OBSERVE_DATA_MISSING", "entry_score": 0, "exit_score": 0}
|
||||
|
||||
mode = item.get("entryMode", "")
|
||||
ac_gate = item.get("acGate", "")
|
||||
rw_partial = item.get("rwPartial", 0) or 0
|
||||
days_to_time_stop = item.get("daysToTimeStop")
|
||||
|
||||
if ac_gate == "BLOCK" and days_to_time_stop is not None and days_to_time_stop <= 5:
|
||||
return {"action": "STOP_OR_TIME_EXIT_READY", "entry_score": 50, "exit_score": 85}
|
||||
|
||||
if rw_partial == 2 or item.get("ma20Slope", 0) < 0 and item.get("disparity", 0) > 8:
|
||||
return {"action": "EXIT_REVIEW", "entry_score": 40, "exit_score": 60}
|
||||
|
||||
if mode == "BREAKOUT" and ac_gate == "CLEAR":
|
||||
return {"action": "BUY_BREAKOUT_PILOT_ONLY", "entry_score": 80, "exit_score": 10}
|
||||
|
||||
if mode == "PULLBACK":
|
||||
return {"action": "BUY_PULLBACK_WAIT", "entry_score": 65, "exit_score": 20}
|
||||
|
||||
return {"action": "OBSERVE", "entry_score": 50, "exit_score": 20}
|
||||
|
||||
def compute_final_decision(item: dict) -> dict:
|
||||
sell_action = item.get("sellAction", "HOLD")
|
||||
allowed_action = item.get("allowedAction", "")
|
||||
timing_action = item.get("timingAction", "")
|
||||
dart_risk = item.get("dartRisk", False)
|
||||
|
||||
if sell_action != "HOLD":
|
||||
return {"final_action": sell_action, "action_priority": 1}
|
||||
|
||||
if timing_action in ("STOP_OR_TIME_EXIT_READY", "NO_BUY_OVERHEATED"):
|
||||
priority = 50 if timing_action == "NO_BUY_OVERHEATED" else 10
|
||||
return {"final_action": timing_action, "action_priority": priority}
|
||||
|
||||
if dart_risk:
|
||||
return {"final_action": "EXIT_DART_RISK", "action_priority": 20}
|
||||
|
||||
if allowed_action:
|
||||
return {"final_action": allowed_action, "action_priority": 30}
|
||||
|
||||
return {"final_action": "HOLD", "action_priority": 99}
|
||||
@@ -5,8 +5,11 @@ import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SRC = ROOT / "src"
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
if str(SRC) not in sys.path:
|
||||
sys.path.insert(0, str(SRC))
|
||||
|
||||
from src.quant_engine.exit_decisions import compute_sell_decision
|
||||
from src.quant_engine.exit_decisions import compute_stop_action_ladder
|
||||
|
||||
@@ -6,8 +6,11 @@ import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SRC = ROOT / "src"
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
if str(SRC) not in sys.path:
|
||||
sys.path.insert(0, str(SRC))
|
||||
|
||||
|
||||
def run_route_flow_simulation(h: dict, df: dict, h1: dict) -> tuple[str, list[dict]]:
|
||||
|
||||
@@ -5,8 +5,11 @@ import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SRC = ROOT / "src"
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
if str(SRC) not in sys.path:
|
||||
sys.path.insert(0, str(SRC))
|
||||
|
||||
from src.quant_engine.exit_decisions import compute_timing_decision
|
||||
|
||||
|
||||
Reference in New Issue
Block a user