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;
}
// 타입 추론이 string[]이 아닌 readonly ["BTC", "ETH"]로 됨
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
// Nullable 타입
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 우선의 스키마 검증을 제공합니다:
// schemas/order.ts
import { z } from 'zod';
// 스키마 정의
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 상태 관리 - 상태 관리 타입
교차 시리즈 추천
- 알고리즘 거래 - 타입 안전 알고리즘 시스템