股票交易 App(Stock trading app)面臨一些獨特的技術挑戰。它們不只是另一種普通的應用程式,而是節奏快速的交易平台,在這裡時間就是一切;能否即時將準確的資訊傳遞給使用者,往往直接影響他們的財務決策。圖 1 展示了業界幾款熱門的股票交易 App。

Figure 1: Popular stock trading apps

核心挑戰在於處理高頻率的資料串流(high-frequency data streams)。每一次價格變動與市場波動都必須以最低延遲(latency)送達使用者,因為即便是幾秒鐘的延遲,都可能意味著錯失交易機會。

而這不只是「快」而已。這類 App 處理的是真金白銀與敏感的金融資訊,因此安全性與可靠性必須同樣穩固。我們需要打造強健的安全機制、可靠的 API,並確保遵循金融法規(financial regulations),讓系統平穩且安全地運作。

Step 1:理解問題並定義設計範圍#

為了釐清我們要打造什麼,讓我們想像一段與面試官的簡短對話。這段來回有助於我們定義 App 的主要功能並設定清楚的邊界。

  • 候選人:在開始之前,我想先了解交易功能的範圍。我們這個股票交易 App 聚焦在什麼?只有股票,還是也要考慮選擇權(options)和期貨(futures)等其他金融商品?
  • 面試官:先保持單純,專注在股票就好。
  • 候選人:好的。我想主要功能會包括下單與取消訂單、查看投資組合(portfolio)、顯示即時價格、以及歷史資料。這樣涵蓋到了嗎?
  • 面試官:差不多了!再加上一個簡單的歷史股價走勢圖(chart),讓使用者更能掌握行情。
  • 候選人:要加上價格警示(price alerts)的推播通知(push notifications)嗎?另外規模大致是多大?是跨多個市場的大型使用者基數嗎?
  • 面試官:通知一定要。規模上大約是 500k 日活躍使用者(DAU),橫跨不同市場。App 需要處理數千支股票的行情並即時更新價格。
  • 候選人:考量到安全與合規要求,我建議盡量不要在裝置上儲存敏感資料,並實作強化的安全措施。認證(authentication)要納入範圍嗎?
  • 面試官:好主意。我們假設使用者已經完成認證。技術上就當作是一個全新(greenfield)專案來設計。

功能需求(Requirements)#

根據上述對話,我們的股票交易 App 需要提供:

  • 使用者可以查看自己的投資組合與股票的即時價格。
  • 使用者可以透過下單與取消訂單來買賣股票。
  • 使用者可以在圖表中查看歷史股價資料。
  • 系統能夠發送價格警示通知

**非功能性需求(non-functional requirements)**方面,我們需要建立一個能夠確保以下特性的系統:

  • 即時資料(Real-time data):系統必須以最低延遲推送市場更新,確保使用者在做交易決策時看到的是最新資訊。
  • 效能(Performance):即使在處理串流資料與複雜視覺化時,UI 也必須維持流暢;交易需要快速且可靠地執行。
  • 安全(Security):金融資料必須全程受到保護,具備強健的身份驗證機制,並完全符合金融監管標準。

**超出範圍(out of scope)**的項目:

  • 選擇權與期貨交易。
  • 使用者註冊。

UI 草圖#

讓我們想像一下 App 的樣貌。圖 2 呈現主要畫面:

  • Home 畫面(左):一眼呈現使用者的財務快照、投資組合與近期訂單。
  • Stock Detail 畫面(中):深入呈現特定股票的歷史資料與交易選項。
  • Place Order modal(右):提供精簡的買入與賣出流程。

Figure 2: Basic sketch of the stock trading mobile app

Step 2:API 設計#

接下來我們要確立 Client 與 Backend 之間的通訊方式,聚焦在通訊協定、互動方式與核心資料模型。

選擇合適的通訊協定#

股票交易 App 需要兩種主要互動模式:

  1. 標準的 Client-Server 交換:例如送出訂單或取得投資組合詳情。
  2. 即時更新:例如即時股價串流。

為了高效滿足這些需求,我們採用混合協定策略(hybrid protocol strategy)

  • HTTP + REST APIs:適合由 Client 發起的動作,如下單、查詢帳戶、取得歷史資料。REST 提供結構化的 request-response 模型,在行動開發中被廣泛採用。
  • WebSockets:適合交付即時資料,例如股價變動與訂單狀態通知。透過維持持久性雙向連線(persistent bidirectional connection),Server 可以即時將更新推播到 Client,確保時效性資訊低延遲送達。

這種組合在標準的請求-回應模式與連續資料串流之間取得良好平衡。許多交易平台都採用這套技術堆疊,證明其在規模上的有效性。

Endpoints 與資料模型#

Stocks#

以下 endpoints 提供股票資訊與歷史走勢的存取:

  • GET /v1/stocks/{symbol}:取得指定股票的詳細資料,包括當前價格、當日高低點、漲跌百分比。
  • GET /v1/stocks/{symbol}/history?interval={interval}&period={period}:提供歷史價格資料,可透過參數自訂時間間隔(如 1m 代表一分鐘、1d 代表一天)與時間範圍(如 1wk 代表一週、3mo 代表三個月)。

對應的資料模型:

KotlinSwift
data class Stock
id: String
symbol: String
name: String
currentPrice: BigDecimal
changePercent: BigDecimal
highPrice: BigDecimal
struct Stock
id: String
symbol: String
name: String
currentPrice: Decimal
changePercent: Decimal
highPrice: Decimal

在股票交易平台這類金融 App 中,務必使用 Kotlin 的 BigDecimal 或 Swift 的 Decimal,而不是 FloatDouble

浮點數型別(Float/Double)會引入微妙的捨入誤差,這在金融情境中是無法接受的。BigDecimal 提供精確的十進位表示與精確的算術運算。雖然這些運算比原生型別稍慢,但在維護資料完整性(data integrity)上,這份保證是必要的。

Portfolio 與 Orders#

以下 endpoints 負責投資組合管理與交易:

  • GET /v1/portfolio:取得使用者當前的投資組合,包含持有的股票與數量。
  • GET /v1/orders?status={status}&page={page}&limit={limit}:回傳分頁的訂單列表,可依狀態過濾(如 open、executed、canceled)。
  • POST /v1/orders:建立一筆新的買入或賣出訂單。
  • DELETE /v1/orders/{orderId}:取消一筆既有訂單。

訂單歷史採用 offset pagination(偏移量分頁)。這種方式在此場景運作良好,因為:

  • 訂單在建立後很少變動。
  • 使用者通常關心近期交易活動,offset pagination 能有效率地存取最新訂單。

資料模型定義如下:

KotlinSwift
data class Portfolio
stocks: List<PortfolioStock>
struct Portfolio
stocks: [PortfolioStock]
data class PortfolioStock
stock: Stock
quantity: BigDecimal
averagePrice: BigDecimal
struct PortfolioStock
stock: Stock
quantity: Decimal
averagePrice: Decimal
data class Order
id: String
stockSymbol: String
quantity: BigDecimal
status: OrderStatus
createdAt: String
struct Order
id: String
stockSymbol: String
quantity: Decimal
status: OrderStatus
createdAt: String
enum class OrderStatus
OPEN, EXECUTED, CANCELED
enum OrderStatus
case open, executed, canceled

Notifications#

為了支援價格警示,我們提供:

  • GET /v1/notifications:取得所有已建立的警示。
  • POST /v1/notifications:為某支股票建立一個新的價格警示。
  • DELETE /v1/notifications/{id}:刪除既有警示。

WebSocket 連線#

為支援即時功能,我們在 wss://api.stocktrading.com/ws 建立 WebSocket 連線,支援的訊息類型包括:

  • stock-prices:串流訂閱股票的即時價格更新。
  • order-updates:回報訂單狀態變化。
  • portfolio-updates:反映因市場波動造成的投資組合價值變動。

Client 透過送出如下事件來訂閱更新:

{
  "action": "subscribe",
  "channels": [
    {
      "name": "stock-prices",
      "symbols": ["AAPL", "GOOGL", "MSFT"]
    }
  ]
}

Step 3:高階 Client 架構#

API 設計完成後,讓我們發展高階的行動端架構。圖 3 呈現此架構,箭頭顯示資料在系統中的流動方向。

Figure 3: Stock trading high-level mobile architecture

外部 Server 端元件#

在進入 Client 本身之前,先看看它依賴的外部元件:

  • Backend:透過 REST API 與 WebSocket 與 Client 溝通,管理請求並維持資料一致性。
  • CDN:對靜態內容(如歷史資料或圖表資源)進行邊緣快取,加速使用者存取。
  • Push Notifications Provider:即使在 App 背景,也能及時將價格變動或訂單更新通知使用者。
  • API Gateway:作為系統的守門員,負責認證與流量管控,保護 Backend 不被過載或惡意攻擊。

API Gateway#

在股票交易 App 中,安全是不可妥協的。我們需要強健的身份驗證、謹慎的請求監控以及嚴格的政策執行,同時維持流暢的使用者體驗。這正是 API Gateway 發揮關鍵作用的地方。

作為系統的「前門(front door)」,API Gateway 驗證並路由所有進入的 Client 請求至相應的 Backend 服務,並在任何可疑或未授權的請求到達核心系統前加以阻擋。

API Gateway 負責幾項關鍵職責:

  • 安全(Security):管理使用者認證與授權、實施速率限制(rate limiting)、偵測可疑 Client。
  • 流量管理(Traffic management):跨服務做負載平衡、控制請求量,避免尖峰交易時段系統過載。
  • 請求處理(Request processing):驗證所有進入的請求、必要時進行資料格式轉換、管理 request/response 標頭。
  • 監控(Monitoring):追蹤 API 使用量、效能指標與錯誤率,以維護系統健康並及早發現潛在問題。
  • 簡化(Simplification):提供乾淨、統一的 API,讓行動 Client 不需要直接面對 Backend 架構的複雜性。

Client 架構#

我們的 App 採用清晰的分層架構(layered architecture),明確分離 UI 層與 Data 層的職責,讓程式碼更易維護與測試。

UI 層(UI layer) 由三個主要畫面組成,每個畫面都有自己的 state holder 來管理展示邏輯:

  • Home screen:作為 App 的中央樞紐,顯示使用者的投資組合概覽與近期交易活動。
  • Stock Detail screen:提供個別股票的完整資訊,包括價格圖表與關鍵指標。
  • Place Order modal:提供精簡的買賣交易介面。

Data 層(data layer) 在幕後由三個專責的 repository 處理核心業務資料:

  • Portfolio repository:作為使用者持股的單一真實來源(single source of truth),並持續與即時更新同步。
  • Stocks repository:管理市場資料,確保使用者總是看到最新的報價資訊。
  • Orders repository:處理所有交易的建立、追蹤與執行狀態。

此外,Push Notifications Client 元件負責監聽 Backend 發出的重要警示,確保使用者即時收到價格變動、訂單完成與其他關鍵事件的通知。

資料儲存#

在股票交易 App 中,不同類型資料對新鮮度的要求各不相同:

  • 即時市場資料僅採用記憶體內快取(in-memory caching),避免寫入磁碟。這個方式能:
    • 確保使用者在做交易決策時看到最新報價。
    • 維持金融 App 所要求的資料完整性。
  • 時效性較低的資訊(如歷史圖表與使用者偏好):採用有明確過期策略的磁碟快取(disk caching),在效能與準確度之間取得平衡。

將即時行情只放在記憶體、不落地磁碟,是金融 App 避免顯示過期價格的常見做法,也能降低敏感資料外洩風險。

Step 4:深入探討#

App 中有些獨特面向值得特別關注。讓我們仔細探討建立歷史股價圖表即時股價資料兩個主題。

建立歷史股價圖表#

視覺化歷史價格資料對任何股票交易 App 都是必要的。歷史股價資料通常提供多種時間區間選擇,例如日、週、月、年與 Max 視圖,每種都提供不同的交易洞察(如圖 4)。

Figure 4: Screenshot of a historical stock price chart

擷取自 eToro Android App

資料密度考量#

每種時間區間都有獨特的資料處理挑戰。例如:

  • 1 天的圖表可能需要 390 個資料點(6.5 小時交易時段內每分鐘一筆)。
  • 5 年的圖表理論上可能包含超過 1,250 個日資料點或超過 260 個週資料點。

實作歷史圖表的一個關鍵挑戰是妥善管理資料密度(data density)。資料點越多細節越豐富,但也帶來效能成本:

  • 高密度資料(如一個月內每分鐘的價格):可能造成渲染效能問題與過高的記憶體使用。
  • 資料點太少:可能錯過重要的價格波動,造成誤導的視覺呈現。

產業洞察:不同股票交易 App 提供不同的使用者體驗,許多產品決策會影響圖表的設計與呈現。Robinhood 與 eTrade 在其官方說明中詳細介紹各自的圖表運作方式,值得深入參考。

圖表的實作方式#

在行動裝置上呈現歷史股票圖表,結合了大量資料集與有限螢幕空間,帶來獨特挑戰。表 1 比較幾種可行方案:

選項說明優點缺點
原生繪圖(Native graphics rendering)使用平台繪圖 API(如 Android Canvas、iOS Core Graphics)手動繪製圖表元素對視覺與效能有完全控制;記憶體足跡最小;直接硬體加速實作複雜,需要平台專精;開發時間長;跨平台程式碼重複
第三方函式庫(Third-party libraries)使用成熟的行動圖表函式庫(如 MPAndroidChart、ChartsOrg/Charts)提供現成元件實作快速,內建縮放、手勢等功能;文件齊全且維護良好相較原生方案有效能開銷;與原生元件整合可能有挑戰
WebView 渲染(WebView-based rendering)在 WebView 嵌入 Web 工具(如 D3.js、Chart.js)或專業產品(如 TradingView)可使用豐富的 Web 圖表生態系;跨平台實作一致效能開銷與較高的記憶體使用;WebView 與原生整合有挑戰;原生感較弱
Server 端渲染(Image API)在 Server 產出圖表並以圖片形式傳給 ClientClient 端處理需求最小;跨裝置行為一致互動性有限;依賴網路;Server 成本與頻寬較高

Table 1:在 Client 呈現歷史股價圖表的各種取捨

考量我們的商業情境中多種 Client 共用同一 Backend,我們決定採用 WebView-based rendering 實作圖表。這個方式讓我們能跨平台維持一致的視覺呈現,並利用強大的 Web 端金融圖表函式庫。

最佳化 WebView 歷史圖表的實作#

使用 WebView 實作圖表帶來幾個重要挑戰,我們需要一一面對。

效能影響#

WebView 雖有彈性,但使用 D3.js 或 ECharts 等複雜 JavaScript 圖表函式庫可能拖累裝置資源。緩解策略:

  • 將圖表與高優先流程(如下單執行)隔離開
  • Server 端預先處理資料,減輕 Client 負擔。
  • 精簡更新流程,避免重複重繪。
  • 在初始完整載入後,對圖表執行 delta updates(差異更新)

歷史股價資料密度也帶來獨特挑戰,建議採用:

  • 在資料傳入 WebView 前先做預處理,減少 JavaScript 處理時間,特別是長時間範圍時。
  • 採用分階段載入(phased loading):先載入低解析度資料,接著依需求載入更高解析度細節。
互動與反應速度#

WebView 所呈現的圖表在處理複雜互動時,可能不如原生實作順暢。為確保表現:

  • 把運算盡量卸載到 Backend 或背景執行緒
  • 在多種裝置上驗證效能
視覺整合#

WebView 元件有時會與原生 UI 顯得脫節。為了創造一致的體驗:

  • 讓圖表樣式對齊 App 的設計系統
  • 調校 WebView 屬性以達成一致的視覺。
  • 遵循平台 UI 最佳實踐,讓使用者感到熟悉。
平台特定挑戰#

每個作業系統都有獨特的障礙:

  • Android
    • WebView 獨立於 App 更新。
    • 行為可能因裝置與 Android 版本而異。
    • 需要跨不同 WebView 實作進行測試。
  • iOS
    • WKWebView 有特定的效能特性。
    • 本地儲存(local storage)的持久化需要謹慎處理。
    • 記憶體管理需特別留意。

產業洞察

  • eToro 選擇整合 Web 端的 TradingView 圖表工具,透過 Web 元件在 App 中提供進階互動圖表。
  • Robinhood 則使用原生繪圖 API 以最大化效能與控制,並在 Android 上開源了一個自訂的 sparkline 圖表函式庫 Spark

即時股價資料#

股價在交易時段內快速變動,經常每秒變化多次。為呈現這些動態變動,股票交易 App 通常採用盤中(intraday)圖表,如圖 5 所示。

Figure 5: Screenshot of an intraday stock price chart

擷取自 Trade Republic Android App

為了維持 App 內一致性,我們將歷史與盤中即時資料視覺化都採用相同的 WebView-based 方式。這提供統一的視覺體驗,也簡化維護,因為我們只需專注在同一套圖表技術堆疊。

即時圖表更新的設計選項#

要把即時資料整合進 WebView 圖表,我們需要一個有效率的更新策略。每種方式對電池、處理能力與記憶體使用都有不同影響。表 2 列出各選項:

選項說明優點缺點
Direct UI updates(直接更新)每收到 WebSocket 新資料點就立即更新 UI實作最簡單;使用者永遠看到最新資料;延遲最小若更新太頻繁會造成 UI jank;高耗電;可能壓垮主執行緒
Buffered updates(緩衝更新)在 buffer 中收集資料變化,固定間隔更新 UI降低 UI 更新頻率;對電池更友善;平滑頻繁變動顯示最新資料會有小延遲;可能錯過中間價格點;需要仔細調校更新間隔
Priority-based updates(基於優先度的更新)對不同 UI 元素分配不同更新優先度將資源聚焦在最重要的資料;關鍵資訊可更頻繁更新;整體體驗更佳實作較複雜;需要精細調校優先度;若相關資料更新速率不同可能造成困惑

Table 2:即時 UI 更新的各種取捨

對於我們 App 的盤中圖表,我們採用 buffered updates 策略。對單一圖表元素而言,這在反應速度與資源效率間取得最佳平衡。

這個決策假設我們一次只顯示一個帶有即時更新的盤中圖表。如果產品需求改為同時顯示多個圖表,我們就需要重新考慮策略,實作一個結合 priority-based updates 的混合方案。

實作策略#

我們針對 WebView 盤中圖表的 buffered update 實作包含幾個關鍵元件:

  1. Stocks repository 包含 data buffering 邏輯,從 WebSocket 連線收集價格更新並放進 queue。
  2. **排程更新器(scheduled updater)**以固定間隔(例如每 1–2 秒)將緩衝的價格資料傳給 WebView 圖表。
  3. JavaScript bridge 在原生程式碼與 WebView 間高效傳輸市場資料更新。
理解 JavaScript Bridge#

股票圖表在 WebView 中以 JavaScript 執行。由於圖表渲染發生在此 JavaScript 環境,我們需要一種方式把處理過的資料從原生 App 傳進 WebView,這正是 JavaScript bridge 的角色。

JavaScript bridge 在原生 App 與 WebView 圖表之間建立通訊通道。即時股票資料首先透過 WebSocket 進入原生程式碼,而不是直接進入 WebView。這個設計有幾項好處:

  • 原生程式碼更有效率地處理網路連線與資料處理。
  • 可在送出視覺化前,將資料格式化妥當。
  • 可在原生層實作更好的 buffering 策略。

這產生明確的職責分離:

  • 原生程式碼(Native code) 負責:管理 WebSocket 連線、接收與驗證資料、緩衝更新以減少處理開銷、預處理顯示資料、決定何時送出更新。
  • WebView 程式碼 負責:渲染圖表、處理使用者互動(zoom、pan、tap)、處理圖表動畫與轉場。

JavaScript bridge 支援雙向資料流動:

  1. Native → WebView:送出股票更新、設定變更、使用者偏好。
  2. WebView → Native:傳遞使用者互動事件,例如縮放、點擊資料點、或切換功能。

平台實作細節

Buffered updates 需要有效率地管理資料串流,再透過 JavaScript bridge 傳送以更新 WebView。

Android

  • 使用 JavascriptInterface 機制在原生程式碼與 WebView 圖表間建立可靠通訊。
  • 利用 Kotlin Flow 搭配適當的 dispatcher,在主執行緒之外處理 buffering 與 throttling。

iOS

  • 使用 WKUserContentControllerWKScriptMessageHandler 實現 native 到 JavaScript 的通訊。
  • 利用 Combine 框架的 throttledebounce 運算子控制更新頻率。

即時圖表更新的資料流#

圖 6 描繪使用者打開某支股票的盤中圖表並即時更新時的資料旅程:

  1. Stocks remote data source 透過 REST API 取得當日歷史資料,作為基準圖表資料。
  2. 同時,remote data source 建立 WebSocket 連線以接收即時價格更新。
  3. Stocks repository 結合這兩個來源並實作 buffered updates 策略:
    1. Repository 建立資料串流,並以當日歷史資料作為初始值發送。
    2. 當 WebSocket 更新到達時,收集到 repository 內的 buffer。
    3. Repository 並不立即把每筆更新送給訂閱者,而是批次這些更新並以固定間隔發送。
  4. Stock detail state holder 訂閱此緩衝資料串流,並負責透過 JavaScript bridge 更新圖表。
  5. WebView 圖表 渲染這些更新,呈現出平滑、即時的資料視覺化效果。

Figure 6: Intraday stock value chart data flow

常見瓶頸與最佳化#

幾種常見問題仍可能影響即時 UI 效能:

  • WebView 相關瓶頸:頻繁更新 WebView 會觸發昂貴的渲染操作。緩解方式:
    • 設定圖表函式庫使用部分重繪(partial redraws),只更新有變動的元素。
    • 在高頻率更新期間停用動畫以降低渲染開銷。
  • 資料處理瓶頸:處理進來的市場資料可能成為瓶頸,尤其涉及複雜計算時:
    • 把所有資料轉換搬到原生程式碼並在背景執行緒執行
    • 預先計算衍生值,而非在 JavaScript 中計算。
    • 對計算值實作快取,避免重複計算。
  • Bridge 通訊開銷:原生與 WebView 間的每次呼叫都有成本:
    • 批次多筆價格更新,合併為一次 bridge 呼叫。
    • 使用精簡的資料格式以降低序列化/反序列化成本。
    • 維持在新鮮度與效能間取得平衡的一致更新間隔。
  • 間歇性連線(Intermittent connectivity):行動網路天生不可靠:
    • 當連線中斷時,清楚地提示資料已過時
    • 採用**指數退避(exponential backoff)**重連策略。
  • 電池影響:即時更新會顯著影響電池壽命:
    • 當 App 進入背景時停止即時更新
    • 根據電池電量實作自適應節流(adaptive throttling)

隨著 App 複雜度與使用者量增加,即時更新系統也需要能夠水平擴展。我們設計的幾個面向確保可擴展性:

  • WebSocket 訊息過濾:在 Server 端做過濾,確保 Client 只收到它真正需要的資料,即使 App 加入更多功能,也能降低 Client 端處理量。
  • Buffer 大小:Buffer 需要夠大以容納兩次刷新間的所有價格更新,即使在市場劇烈波動時。對多數股票,10–20 個價格點的容量應已足夠。
  • 自適應排程(Adaptive scheduling):根據市場狀況、裝置狀態與使用者參與度調整更新頻率(當使用者積極查看資料且 App 在前景時):
    • 在高波動市場時段提高頻率。
    • 允許使用者依據電池電量與裝置效能調整頻率。

產業洞察

  • J.P. MorganCommerzbank 等機構以及 Binomo 等交易平台,使用 SciChart 的原生引擎來驅動即時股票儀表板。
  • Coinbase 的交易所平台提供 WebSocket feed,廣播訂單與交易的即時市場資料。

Step 5:總結#

在本章,我們設計了一個行動端的股票交易 App,提供投資管理的核心功能。系統支援:

  • 查看即時股價
  • 透過下單與取消訂單執行交易。
  • 透過圖表分析歷史價格資料
  • 接收即時價格警示

架構上,我們結合 REST API(用於標準操作)與 WebSocket(用於即時更新)。採用分層的 Client 架構,將 UI 與資料關注點分離,讓程式碼易於維護與測試。我們也深入探討了歷史與即時盤中股價圖表的實作。

若準備在行動系統設計面試中討論此題,以下延伸主題值得進一步探索:

  • 離線功能(Offline functionality):設計在網路連線有限時的快取與本地操作策略。
  • 跨平台考量:討論此架構如何調整以支援 Web 與其他平台。
  • 法規遵循(Regulatory Compliance):針對不同地區股票交易的合規需求做具體探討。
  • 個人化 UI:提供可自訂的儀表板或觀察清單,以量身打造體驗提升使用者參與度。