TypeScript 5 Trading System Type Safety Practice
Quick Navigation: This article deeply explores TypeScript 5 applications in trading systems, from basic types to advanced techniques, providing a complete guide for creating type-safe, maintainable trading applications. Estimated reading time: 13 minutes.
Why Do Trading Systems Need TypeScript?
In the financial trading field, one type error can lead to millions in losses. Imagine this scenario:
// ❌ JavaScript: Silent error
const order = {
symbol: 'BTC/USDT',
side: 'buy',
quantity: '0.5', // String! Should be number
price: 50000
};
// Error when calculating total
const total = order.quantity * order.price; // NaN
TypeScript 5 provides compile-time type checking, catching such errors before code runs. According to GitHub research, TypeScript can reduce runtime errors by 15%.
TypeScript 5 Core Advantages
| Advantage | Description | Trading System Value |
|:---|:---|:---|
| Compile-time Checking | Errors caught at compile time | Avoid production crashes |
| IntelliSense | IDE auto-completion and docs | Improve dev efficiency 30% |
| Safe Refactoring | Rename, extract functions safely | Large-scale refactoring without fear |
| Self-documenting Code | Types are documentation | Reduce maintenance cost |
| Team Collaboration | Interfaces are contracts | Reduce communication cost |
TypeScript 5 New Features
Decorators
// Trade operation log decorator
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> {
// Implementation
}
}
const Type Parameters
// TypeScript 5 new syntax: More precise type inference
function createTradePair<const T extends readonly string[]>(symbols: T): T {
return symbols;
}
// Type inferred as readonly ["BTC", "ETH"] rather than string[]
const pairs = createTradePair(["BTC", "ETH"] as const);
Better Type Inference
// TypeScript 5 improved generic inference
function createBotConfig<T extends BotStrategy>(config: T) {
return config;
}
// Automatically infers complete type, no explicit specification needed
const config = createBotConfig({
strategy: 'EMA_CROSS',
params: { fast: 10, slow: 20 },
risk: { maxPosition: 0.1 }
});
Trading System Type Design
Core Domain Types
// types/trading.ts
// Basic types
export type Symbol = string & { __brand: 'Symbol' };
export type OrderId = string & { __brand: 'OrderId' };
export type BotId = string & { __brand: 'BotId' };
// Trading pair
export interface TradingPair {
symbol: Symbol;
baseAsset: string;
quoteAsset: string;
pricePrecision: number;
quantityPrecision: number;
minQuantity: number;
maxQuantity: number;
}
// Order side
export type OrderSide = 'BUY' | 'SELL';
// Order type
export type OrderType =
| 'MARKET' // Market order
| 'LIMIT' // Limit order
| 'STOP_LOSS' // Stop loss order
| 'TAKE_PROFIT'; // Take profit order
// Order status
export type OrderStatus =
| 'PENDING' // Pending
| 'OPEN' // Open
| 'PARTIALLY_FILLED' // Partially filled
| 'FILLED' // Filled
| 'CANCELLED' // Cancelled
| 'REJECTED'; // Rejected
// Order request
export interface OrderRequest {
symbol: Symbol;
side: OrderSide;
type: OrderType;
quantity: number;
price?: number; // Required for limit orders
stopPrice?: number; // Required for stop/take profit orders
timeInForce?: 'GTC' | 'IOC' | 'FOK';
clientOrderId?: string;
}
// Order response
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;
}
Advanced Generic Applications
// types/utils.ts
// Nullable types
export type Nullable<T> = T | null;
export type Optional<T> = T | undefined;
// API response wrapper
export interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
timestamp: number;
}
export interface ApiError {
code: string;
message: string;
details?: Record<string, unknown>;
}
// Paginated response
export interface PaginatedResponse<T> {
data: T[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
};
}
// Event type
export type EventPayload<E extends { type: string; payload: unknown }> = E['payload'];
// Trade event
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 Best Practices
Component Props Types
// 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 ? 'Processing...' : children}
</button>
);
}
Custom Hooks Types
// 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,
});
}
// Usage
const { data: bot, error, isLoading } = useBot('bot-123', {
refetchInterval: 5000,
});
Context Types
// 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 Runtime Validation
Why Need Zod?
TypeScript only checks at compile time, runtime data still needs validation. Zod provides TypeScript-first schema validation:
// schemas/order.ts
import { z } from 'zod';
// Define 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'),
});
// Derive type
export type OrderRequest = z.infer<typeof orderRequestSchema>;
// Runtime validation
function validateOrder(data: unknown): OrderRequest {
return orderRequestSchema.parse(data);
}
// Safe parsing (no throw)
function safeValidateOrder(data: unknown) {
return orderRequestSchema.safeParse(data);
}
API Response Validation
// 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(),
});
// Usage
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;
}
Type-Safe Error Handling
Result Type Pattern
// types/result.ts
export type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
// Usage
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')
};
}
}
// Caller handling
const result = await placeOrder(orderRequest);
if (result.success) {
console.log('Order placed:', result.data.orderId);
} else {
console.error('Order failed:', result.error.message);
}
Real-world Case: Sentinel Bot Type System
Project Type Structure
src/
├── types/
│ ├── index.ts # Public exports
│ ├── trading.ts # Trading domain
│ ├── api.ts # API types
│ ├── websocket.ts # WebSocket types
│ └── utils.ts # Utility types
├── schemas/
│ ├── index.ts
│ ├── order.ts # Order validation
│ ├── bot.ts # Bot validation
│ └── api.ts # API response validation
└── components/
└── **/*.types.ts # Component-specific types
Type Coverage
| Module | Type Coverage | Notes |
|:---|:---:|:---|
| API Layer | 100% | All APIs have type definitions |
| Components | 98% | Props + State complete types |
| Store | 100% | Zustand + TypeScript |
| Utility Functions | 95% | Generic functions complete types |
Frequently Asked Questions FAQ
Q1: Is TypeScript learning curve steep?
A: Progressive learning:
- Start with
any(allowed but warned) - Gradually add basic types
- Learn interfaces and type aliases
- Master generics and advanced techniques
Q2: How to handle third-party libraries without types?
A: Declare modules:
// types/library.d.ts
declare module 'some-library' {
export function doSomething(): void;
}
Or install @types/ packages.
Q3: Type checking too slow what to do?
A: Optimization strategies:
- Use Project References to split large projects
- Enable incremental compilation
- Exclude node_modules
- Use
skipLibCheck: true
Q4: Can any type be used?
A: Try to avoid, but acceptable in these cases:
- Migration transition period
- Third-party libraries without types and cannot declare
- Extremely complex dynamic logic (needs documentation)
Q5: When to use generics?
A: When functions/components need to handle multiple types:
// Generic data fetching hook
function useData<T>(fetcher: () => Promise<T>) {
const [data, setData] = useState<T | null>(null);
// ...
}
Q6: How to combine types with runtime validation?
A: TypeScript + Zod golden combination:
- TypeScript: Compile-time checking
- Zod: Runtime validation (API boundaries)
Q7: How to test types?
A: Use tsd or expect-type:
import { expectType } from 'tsd';
expectType<string>(order.symbol);
Q8: Should strict mode be enabled?
A: Strongly recommended:
{
"compilerOptions": {
"strict": true
}
}
May have many errors initially, but huge long-term benefits.
Conclusion and Action Recommendations
TypeScript 5 brings to trading systems:
- ✅ Compile-time error catching
- ✅ Intelligent development experience
- ✅ Safe refactoring capability
- ✅ Self-documenting code
Take Action Now
- [ ] Enable strict mode
- [ ] Define core domain types
- [ ] Establish API type contracts
- [ ] Integrate Zod runtime validation
- [ ] Establish type checking CI flow
Extended Reading:
Author: Sentinel Team
Last Updated: 2026-03-04
Technical Verification: Based on Sentinel Bot production environment practical experience
Improving your trading system code quality? Experience Sentinel Bot's TypeScript 5 driven architecture now, or download our type templates to get started quickly.
Free Trial Sentinel Bot | Download Type Templates | Technical Consultation
Related Articles
Same Series Extended Reading
- React 18 Trading Interface - React type integration
- Zustand State Management - State management types
Cross-series Recommendations
- Algorithmic Trading - Type-safe algorithm system