教學 進階

TypeScript 5 交易系統型別安全實踐|React 型別系統與程式碼品質提升指南

Sentinel Team · 2026-03-04
TypeScript 5 交易系統型別安全實踐|React 型別系統與程式碼品質提升指南

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: 漸進式學習:

  1. 先從 any 開始(允許但警告)
  2. 逐步添加基本型別
  3. 學習介面與型別別名
  4. 掌握泛型與進階技巧

Q2: 如何處理第三方庫無型別?

A: 宣告模組:

// types/library.d.ts
declare module 'some-library' {
  export function doSomething(): void;
}

或安裝 @types/ 套件。

Q3: 型別檢查太慢怎麼辦?

A: 優化策略:

Q4: any 型別可以用嗎?

A: 盡量避免,但以下情況可接受:

Q5: 泛型什麼時候使用?

A: 當函式/組件需要處理多種型別時:

// 通用資料獲取 hook
function useData<T>(fetcher: () => Promise<T>) {
  const [data, setData] = useState<T | null>(null);
  // ...
}

Q6: 型別與執行期驗證如何結合?

A: TypeScript + Zod 黃金組合:

Q7: 如何測試型別?

A: 使用 tsdexpect-type

import { expectType } from 'tsd';

expectType<string>(order.symbol);

Q8: strict 模式要開嗎?

A: 強烈建議開啟

{
  "compilerOptions": {
    "strict": true
  }
}

初期可能有很多錯誤,但長期收益巨大。


結論與行動建議

TypeScript 5 為交易系統帶來:

立即行動


延伸閱讀


作者:Sentinel Team

最後更新:2026-03-04

技術驗證:本文基於 Sentinel Bot 生產環境實戰經驗


正在提升交易系統程式碼品質?立即體驗 Sentinel Bot 的 TypeScript 5 驅動架構,或下載我們的型別模板快速開始。

免費試用 Sentinel Bot | 下載型別模板 | 技術諮詢


相關文章

同系列延伸閱讀

跨系列推薦