📈 AI 驅動的投資組合風險平台:Django、FastAPI、React、Flutter 全棧微服務與自動化 CI/CD 實踐
前言
在當今快節奏的金融市場中,及時、精準的風險評估對於投資決策至關重要。然而,傳統的手動數據處理和報告生成流程不僅耗時,還容易出錯。為了解決這個痛點,我們開發了一個 AI 驅動的投資組合風險評估與報告平台。
您可以透過以下 GitHub 連結檢閱本專案的原始碼:https://github.com/BpsEason/investment-risk-platform.git
這個專案不僅是一個功能完整的應用程式,更是一個展示現代軟體工程實踐的典範,涵蓋了全棧微服務架構、自動化開發流程、多層次測試與完整的 CI/CD 管道。本文將深入剖析專案的技術亮點、關鍵程式碼,並解答開發過程中常見的問題。
專案核心技術與亮點
我們的平台融合了多種前沿技術,旨在實現高效率、高品質的開發與運維。
1. 🤖 AI 驅動程式碼生成 (AI-Driven Code Generation)
核心思想: 藉由
generate_ai_code.py
腳本,我們將程式碼生成過程交給 AI。這不僅是自動化,更是知識傳承的自動化。AI 能根據預設好的 Prompt (*/prompts/*.txt
),快速產出符合規範的 Django Model、DRF Serializer、React 組件、Flutter Widget 及其對應的測試文件。優勢: 極大加速專案啟動速度(Scaffold),確保程式碼風格與結構一致,並將開發者的精力從重複性工作解放出來,專注於核心業務邏輯的實現。
2. 🚀 全棧微服務架構 (Full-Stack Microservices Architecture)
解耦與專業化: 專案採用了前後端分離與微服務的架構,各服務各司其職,易於維護與擴展。
Django: 擔任核心業務後台,負責金融風險指標的精準計算(如 VaR, CVaR, Sharpe Ratio),並提供 RESTful API。其強大的 ORM 和 Admin 介面非常適合處理業務邏輯與資料庫管理。
FastAPI: 作為高效能的數據 ETL 層,專門處理來自 CSV/Excel 等檔案的大量數據匯入與預處理。其異步 (Asynchronous) 能力確保了 I/O 密集型任務的高吞吐量。
React + D3.js: 構建網頁儀表板,利用 D3.js 實現互動式數據可視化,為決策者提供直觀的數據洞察。
Flutter: 開發原生行動應用,為使用者提供流暢的跨平台體驗。
3. ✅ 自動化測試與 CI/CD (Automated Testing & CI/CD)
品質保證: 專案實現了多層次測試,涵蓋單元測試、集成測試和 E2E 測試。
深度驗證: 透過 Mutation Testing (Mutpy for Python, Stryker for JS) 來驗證測試的有效性,確保測試不是「假性」通過,而是真正能夠捕捉到程式碼的錯誤。
持續整合: 藉助 GitHub Actions,每次提交程式碼都會自動觸發一連串的測試、程式碼品質檢查與覆蓋率報告生成(整合 Codecov)。CI/CD 流程還包含 API 文件可用性檢查,確保 API 文件的正確性。
關鍵程式碼解析
這部分將深入探討專案中幾個核心模組的程式碼實現,展現專案的設計巧思。
1. Django Model: PortfolioRisk
PortfolioRisk
模型負責儲存每一次的風險計算結果。其設計亮點在於使用了 UUIDField
作為主鍵,並結合 unique_together
來確保數據的完整性。
# backend/django_risk_app/risk_metrics/models.py
import uuid
from django.db import models
# AI 生成程式碼開始...
class PortfolioRisk(models.Model):
portfolio_id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False,
verbose_name="投資組合 ID"
)
metric = models.CharField(max_length=100, verbose_name="風險指標")
value = models.FloatField(verbose_name="指標數值")
calculated_at = models.DateTimeField(auto_now_add=True, verbose_name="計算時間")
class Meta:
verbose_name = "投資組合風險"
verbose_name_plural = "投資組合風險"
unique_together = ("portfolio_id", "metric")
ordering = ['-calculated_at']
def __str__(self):
return f"{self.portfolio_id} – {self.metric}: {self.value:.4f}"
# AI 生成程式碼結束
解析:
portfolio_id
: 使用UUIDField
而非傳統的整數主鍵,可以避免多個服務在創建資料時的 ID 衝突,尤其適合微服務架構。unique_together
: 確保同一個投資組合不會重複計算同一個指標,防止數據冗餘。ordering
: 預設按計算時間降序排列,這對於 API 獲取最新數據非常有用。
2. FastAPI 端點: /calculate-risk
與 /etl/import-data
FastAPI 服務展示了其在處理數據 I/O 與計算方面的強大能力。
# backend/fastapi_etl_service/main.py (部分)
# ...
@app.post("/calculate-risk", response_model=RiskCalculationResponse, tags=["risk_calculation"])
async def calculate_risk(request: RiskCalculationRequest):
"""根據歷史價格計算指定風險指標"""
# ... 數據驗證邏輯 ...
prices = pd.Series(d.get("price", 0) for d in request.data)
returns = prices.pct_change().dropna()
if returns.empty:
raise HTTPException(status_code=400, detail="數據不足以計算收益率。")
calculated_value = 0.0
# ... VaR, CVaR, Sharpe Ratio 計算邏輯 ...
return RiskCalculationResponse(
metric=request.metric,
value=float(calculated_value),
unit=unit,
description=description
)
@app.post("/etl/import-data", tags=["etl"])
async def import_data(file: UploadFile = File(...)):
"""接收 CSV 或 Excel 文件並模擬匯入數據"""
if not file.filename:
raise HTTPException(status_code=400, detail="未提供文件名。")
try:
if file.filename.endswith('.csv'):
df = pd.read_csv(io.StringIO((await file.read()).decode('utf-8')))
elif file.filename.endswith(('.xls', '.xlsx')):
df = pd.read_excel(io.BytesIO(await file.read()))
else:
raise HTTPException(status_code=400, detail="只支援 CSV 或 XLSX 格式的文件。")
return {"message": f"文件 '{file.filename}' 已成功模擬匯入", "rows_processed": len(df.index)}
except Exception as e:
raise HTTPException(status_code=500, detail=f"文件處理失敗: {e}")
解析:
calculate_risk
: 使用pandas
和numpy
進行底層的金融計算,展示了 Python 在科學計算生態的優勢。其同步的計算流程可以根據需要,調整為異步或交由 Celery 等後台任務處理,以避免阻塞主執行緒。import-data
: 利用 FastAPI 的UploadFile
和File
依賴注入,輕鬆處理檔案上傳。通過io.StringIO
和io.BytesIO
,將上傳的檔案直接讀取到記憶體中,再交由pandas
處理,流程清晰高效。
3. React 組件: PortfolioRiskDisplay
前端組件負責將後端返回的 JSON 數據轉換為使用者友好的視覺化內容。
// frontend-react/src/components/PortfolioRiskDisplay.js
import React from 'react';
/**
* 風險指標顯示元件
* @param {Array<{ metric: string, value: number }>} riskData
*/
function PortfolioRiskDisplay({ riskData }) {
// ... 數據驗證與渲染邏輯 ...
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 my-6">
{riskData.map(({ metric, value }) => (
<div key={metric} className="p-4 bg-white rounded-lg shadow-lg flex flex-col items-center justify-center">
<h3 className="text-xl font-semibold text-gray-700 mb-2">{metric}</h3>
<p className="mt-2 text-3xl font-bold text-blue-600">
{metric.toLowerCase().includes("ratio")
? value.toFixed(2)
: `${(value * 100).toFixed(2)}%`}
</p>
</div>
))}
</div>
);
}
export default PortfolioRiskDisplay;
解析:
props 傳遞: 通過
riskData
prop 接收數據,遵循 React 的單向數據流原則。樣式化: 結合 Tailwind CSS,實現快速、響應式的 UI 佈局。
數據格式化: 根據指標名稱(如是否包含 "ratio"),動態格式化顯示數值,體現了對數據展示細節的考量。
4. Flutter Widget: RiskMetricCard
RiskMetricCard
是一個可重用的 Widget,展示了 Flutter 在構建清晰 UI 方面的優勢。
// flutter-app/lib/widgets/risk_metric_card.dart
import 'package:flutter/material.dart';
/// 顯示單一風險指標的卡片
class RiskMetricCard extends StatelessWidget {
final String metric;
final double value;
const RiskMetricCard({
Key? key,
required this.metric,
required this.value,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0),
elevation: 6,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
metric,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
),
const SizedBox(height: 8),
Text(
metric.toLowerCase().contains('ratio')
? value.toStringAsFixed(2)
: '${(value * 100).toStringAsFixed(2)}%',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: Colors.blueAccent,
),
),
],
),
),
);
}
}
解析:
Widget 組合: 透過
Card
,Padding
,Column
等 Widget 的嵌套,輕鬆構建出層次分明的卡片樣式。參數化:
final
屬性確保 Widget 的不可變性,符合 Flutter 的最佳實踐。平台原生: 儘管程式碼是單一的,但在 Android 和 iOS 上都能渲染出符合各自平台規範的 UI。
常見問題 (FAQs)
Q1: 為什麼專案後端同時使用 Django 和 FastAPI?
A: 這是一個經典的微服務設計考量。
Django 擅長處理複雜的業務邏輯、ORM、使用者驗證和後台管理(Django Admin)。它適合用來構建穩定、功能豐富的核心應用。
FastAPI 則以其高效能和異步處理能力見長,非常適合擔任輕量級的 API Gateway 或專門處理 I/O 密集型任務(如文件匯入)。這種搭配讓我們能夠充分發揮兩種框架的優勢。
Q2: AI 生成的程式碼是否真的能用於生產環境?
A: AI 生成的程式碼是強大的開發輔助工具,而非終端產品。
我們的流程將 AI 程式碼視為高品質的 Scaffold。
所有生成的程式碼都會被註釋包裹,並附帶
請審閱後移除註釋並使用
的提示。這強調了人為審閱的重要性,確保程式碼符合業務需求、安全規範和團隊風格。
Q3: 專案的金融計算準確性如何保證?
A: 程式碼中的金融計算範例是基於歷史模擬法進行的簡化實現,目的是展示技術框架的可行性。
在實際生產環境中,我們會使用更成熟的金融計算庫,並結合專業的金融模型(如蒙地卡羅模擬、參數化模型等),並通過嚴格的數據驗證和回測,來確保計算的準確性。
Q4: 專案的部署與擴展性如何?
A: 專案的架構設計已充分考慮擴展性。
本地開發: 使用
docker-compose up -d
即可一鍵啟動所有服務,極大簡化開發環境。生產環境: 由於各服務都是獨立的 Docker 容器,可以很方便地將它們部署到 Kubernetes 等容器編排平台,實現彈性擴展、服務發現和負載均衡。
總結
這個「AI 驅動的投資組合風險評估與報告平台」專案不僅展示了多種前沿技術的整合能力,更體現了對軟體開發生命週期的全面考量——從自動化開發、微服務設計,到嚴謹的測試與持續整合。
它是一個功能完整、架構優良的範例專案,也是一個理想的學習與展示平台,證明開發者具備將複雜需求轉化為高效、可維護軟體系統的能力。
沒有留言:
張貼留言