TypeScript 5 交易系統型別安全實踐
快速導覽:本文深入探討 TypeScript 5 在交易系統的應用,從基礎型別到進階技巧,提供打造型別安全、可維護交易應用的完整指南。預計閱讀時間 13 分鐘。
為什麼交易系統需要 TypeScript?
在金融交易領域,一個型別錯誤可能導致數百萬的損失。想像這個場景:
// ❌ JavaScript:無聲的錯誤
const order = {
symbol: 'BTC/USDT',
side: 'buy',
quantity: '0.5', // 字串!應該是數字
price: 50000
};
// 計算總額時出錯
const total = order.quantity * order.price; // NaN
TypeScript 5 提供編譯時型別檢查,在程式碼運行前捕獲這類錯誤。根據 GitHub 研究,TypeScript 能減少 15% 的執行期錯誤。
TypeScript 5 核心優勢
| 優勢 | 說明 | 交易系統價值 |
|:---|:---|:---|
| 編譯時檢查 | 錯誤在編譯期發現 | 避免生產環境崩潰 |
| 智能提示 | IDE 自動完成與文件 | 提升開發效率 30% |
| 重構安全 | 改名、提取函式有保障 | 大規模重構無恐懼 |
| 自描述程式碼 | 型別即文件 | 降低維護成本 |
| 團隊協作 | 介面即契約 | 減少溝通成本 |
TypeScript 5 新特性
裝飾器(Decorators)
// 交易操作日誌裝飾器
function logTrade(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
console.log(`[TRADE] ${propertyKey} called with:`, args);
const result = await originalMethod.apply(this, args);
console.log(`[TRADE] ${propertyKey} result:`, result);
return result;
};
}
class TradingService {
@logTrade
async placeOrder(order: OrderRequest): Promise<OrderResponse> {
// 實作
}
}
const 型別參數
// TypeScript 5 新語法:更精確的型別推斷
function createTradePair<const T extends readonly string[]>(symbols: T): T {
return symbols;
}
// 型別推斷為 readonly ["BTC", "ETH"] 而非 string[]
const pairs = createTradePair(["BTC", "ETH"] as const);
更好的型別推斷
// TypeScript 5 改進了泛型推斷
function createBotConfig<T extends BotStrategy>(config: T) {
return config;
}
// 自動推斷完整型別,無需顯式指定
const config = createBotConfig({
strategy: 'EMA_CROSS',
params: { fast: 10, slow: 20 },
risk: { maxPosition: 0.1 }
});
交易系統型別設計
核心領域型別
// types/trading.ts
// 基礎型別
export type Symbol = string & { __brand: 'Symbol' };
export type OrderId = string & { __brand: 'OrderId' };
export type BotId = string & { __brand: 'BotId' };
// 交易對
export interface TradingPair {
symbol: Symbol;
baseAsset: string;
quoteAsset: string;
pricePrecision: number;
quantityPrecision: number;
minQuantity: number;
maxQuantity: number;
}
// 訂單方向
export type OrderSide = 'BUY' | 'SELL';
// 訂單類型
export type OrderType =
| 'MARKET' // 市價單
| 'LIMIT' // 限價單
| 'STOP_LOSS' // 止損單
| 'TAKE_PROFIT' // 止盈單;
// 訂單狀態
export type OrderStatus =
| 'PENDING' // 待處理
| 'OPEN' // 已開倉
| 'PARTIALLY_FILLED' // 部分成交
| 'FILLED' // 完全成交
| 'CANCELLED' // 已取消
| 'REJECTED'; // 被拒絕
// 訂單請求
export interface OrderRequest {
symbol: Symbol;
side: OrderSide;
type: OrderType;
quantity: number;
price?: number; // 限價單必填
stopPrice?: number; // 止損/止盈單必填
timeInForce?: 'GTC' | 'IOC' | 'FOK';
clientOrderId?: string;
}
// 訂單響應
export interface OrderResponse {
orderId: OrderId;
clientOrderId?: string;
symbol: Symbol;
status: OrderStatus;
side: OrderSide;
type: OrderType;
price: number;
quantity: number;
executedQuantity: number;
createdAt: Date;
updatedAt: Date;
}
進階泛型應用
// types/utils.ts
// 可空的型別
export type Nullable<T> = T | null;
export type Optional<T> = T | undefined;
// API 響應包裝
export interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
timestamp: number;
}
export interface ApiError {
code: string;
message: string;
details?: Record<string, unknown>;
}
// 分頁響應
export interface PaginatedResponse<T> {
data: T[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
};
}
// 事件型別
export type EventPayload<E extends { type: string; payload: unknown }> = E['payload'];
// 交易事件
export interface TradeEvent {
type: 'TRADE_EXECUTED' | 'ORDER_FILLED' | 'POSITION_UPDATED';
payload: {
tradeId: string;
symbol: Symbol;
side: OrderSide;
price: number;
quantity: number;
timestamp: Date;
};
}
React + TypeScript 最佳實踐
組件 Props 型別
// components/OrderButton.tsx
import { ButtonHTMLAttributes } from 'react';
interface OrderButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
side: 'BUY' | 'SELL';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
onOrderSubmit: (side: OrderSide) => void;
}
export function OrderButton({
side,
size = 'md',
isLoading,
onOrderSubmit,
children,
...props
}: OrderButtonProps) {
const variant = side === 'BUY' ? 'success' : 'danger';
return (
<button
type="button"
data-side={side}
data-size={size}
disabled={isLoading}
onClick={() => onOrderSubmit(side)}
{...props}
>
{isLoading ? '處理中...' : children}
</button>
);
}
自定義 Hooks 型別
// hooks/useBot.ts
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
interface UseBotOptions extends Omit<
UseQueryOptions<Bot, Error>,
'queryKey' | 'queryFn'
> {
refetchInterval?: number;
}
export function useBot(id: BotId, options?: UseBotOptions) {
return useQuery<Bot, Error>({
queryKey: ['bots', id],
queryFn: () => api.bots.get(id),
enabled: !!id,
...options,
});
}
// 使用
const { data: bot, error, isLoading } = useBot('bot-123', {
refetchInterval: 5000,
});
Context 型別
// contexts/TradingContext.tsx
import { createContext, useContext } from 'react';
interface TradingContextValue {
currentSymbol: Symbol;
setCurrentSymbol: (symbol: Symbol) => void;
tickSize: number;
lotSize: number;
}
const TradingContext = createContext<TradingContextValue | null>(null);
export function useTrading() {
const context = useContext(TradingContext);
if (!context) {
throw new Error('useTrading must be used within TradingProvider');
}
return context;
}
Zod 執行期驗證
為什麼需要 Zod?
TypeScript 只在編譯時檢查,執行期資料仍需驗證。Zod 提供 TypeScript 優先的 schema 驗證:
// schemas/order.ts
import { z } from 'zod';
// 定義 schema
export const orderRequestSchema = z.object({
symbol: z.string().min(1),
side: z.enum(['BUY', 'SELL']),
type: z.enum(['MARKET', 'LIMIT', 'STOP_LOSS', 'TAKE_PROFIT']),
quantity: z.number().positive(),
price: z.number().positive().optional(),
stopPrice: z.number().positive().optional(),
timeInForce: z.enum(['GTC', 'IOC', 'FOK']).default('GTC'),
});
// 推導型別
export type OrderRequest = z.infer<typeof orderRequestSchema>;
// 執行期驗證
function validateOrder(data: unknown): OrderRequest {
return orderRequestSchema.parse(data);
}
// 安全解析(不拋錯)
function safeValidateOrder(data: unknown) {
return orderRequestSchema.safeParse(data);
}
API 響應驗證
// schemas/api.ts
export const apiResponseSchema = <T extends z.ZodTypeAny>(dataSchema: T) =>
z.object({
success: z.boolean(),
data: dataSchema,
message: z.string().optional(),
timestamp: z.number(),
});
// 使用
const botResponseSchema = apiResponseSchema(botSchema);
async function fetchBot(id: string): Promise<Bot> {
const response = await api.get(`/bots/${id}`);
const parsed = botResponseSchema.parse(response);
return parsed.data;
}
型別安全錯誤處理
Result 型別模式
// types/result.ts
export type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
// 使用
async function placeOrder(request: OrderRequest): Promise<Result<OrderResponse>> {
try {
const response = await api.orders.place(request);
return { success: true, data: response };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error('Unknown error')
};
}
}
// 呼叫端處理
const result = await placeOrder(orderRequest);
if (result.success) {
console.log('Order placed:', result.data.orderId);
} else {
console.error('Order failed:', result.error.message);
}
實戰案例:Sentinel Bot 型別系統
專案型別結構
src/
├── types/
│ ├── index.ts # 公開匯出
│ ├── trading.ts # 交易領域
│ ├── api.ts # API 型別
│ ├── websocket.ts # WebSocket 型別
│ └── utils.ts # 工具型別
├── schemas/
│ ├── index.ts
│ ├── order.ts # 訂單驗證
│ ├── bot.ts # 機器人驗證
│ └── api.ts # API 響應驗證
└── components/
└── **/*.types.ts # 組件專用型別
型別覆蓋率
| 模組 | 型別覆蓋率 | 說明 |
|:---|:---:|:---|
| API 層 | 100% | 所有 API 都有型別定義 |
| 組件 | 98% | Props + State 完整型別 |
| Store | 100% | Zustand + TypeScript |
| 工具函式 | 95% | 泛型函式完整型別 |
常見問題 FAQ
Q1: TypeScript 學習曲線陡峭嗎?
A: 漸進式學習:
- 先從
any開始(允許但警告) - 逐步添加基本型別
- 學習介面與型別別名
- 掌握泛型與進階技巧
Q2: 如何處理第三方庫無型別?
A: 宣告模組:
// types/library.d.ts
declare module 'some-library' {
export function doSomething(): void;
}
或安裝 @types/ 套件。
Q3: 型別檢查太慢怎麼辦?
A: 優化策略:
- 使用 Project References 分割大型專案
- 啟用增量編譯
- 排除 node_modules
- 使用
skipLibCheck: true
Q4: any 型別可以用嗎?
A: 盡量避免,但以下情況可接受:
- 遷移過渡期
- 第三方庫無型別且無法宣告
- 極複雜的動態邏輯(需文件說明)
Q5: 泛型什麼時候使用?
A: 當函式/組件需要處理多種型別時:
// 通用資料獲取 hook
function useData<T>(fetcher: () => Promise<T>) {
const [data, setData] = useState<T | null>(null);
// ...
}
Q6: 型別與執行期驗證如何結合?
A: TypeScript + Zod 黃金組合:
- TypeScript:編譯時檢查
- Zod:執行期驗證(API 邊界)
Q7: 如何測試型別?
A: 使用 tsd 或 expect-type:
import { expectType } from 'tsd';
expectType<string>(order.symbol);
Q8: strict 模式要開嗎?
A: 強烈建議開啟:
{
"compilerOptions": {
"strict": true
}
}
初期可能有很多錯誤,但長期收益巨大。
結論與行動建議
TypeScript 5 為交易系統帶來:
- ✅ 編譯時錯誤捕獲
- ✅ 智能開發體驗
- ✅ 安全重構能力
- ✅ 自描述程式碼
立即行動
- [ ] 啟用 strict 模式
- [ ] 定義核心領域型別
- [ ] 建立 API 型別契約
- [ ] 整合 Zod 執行期驗證
- [ ] 建立型別檢查 CI 流程
延伸閱讀:
作者:Sentinel Team
最後更新:2026-03-04
技術驗證:本文基於 Sentinel Bot 生產環境實戰經驗
正在提升交易系統程式碼品質?立即體驗 Sentinel Bot 的 TypeScript 5 驅動架構,或下載我們的型別模板快速開始。
免費試用 Sentinel Bot | 下載型別模板 | 技術諮詢
相關文章
同系列延伸閱讀
- React 18 交易介面 - React 型別整合
- Zustand 狀態管理 - 狀態管理型別
跨系列推薦
- 算法交易 - 型別安全算法系統