Free & open source — no account required
Free & open source — no account required
This technical guide provides an in-depth analysis of the fix to typescript engine, best practices for implementation, and data security standards.
Unlock the full power of the FinFlow Pro workbench. Specifically designed for FIX, SWIFT MT/MX, and ISO 20022 message inspection with industrial-grade accuracy.
Launch FinFlow ProThe FIX (Financial Information eXchange) protocol uses a tag-value format (35=D\x0149=SENDER\x01) where the SOH character (\x01) separates fields. Converting FIX to TypeScript types requires a FIX data dictionary to map tag numbers to field names and types, special handling for repeating groups (nested arrays of tag-value blocks), and BigDecimal precision for price and quantity fields — JavaScript's number type introduces rounding errors for financial quantities. This guide covers a full FIX 4.4 parser in TypeScript, typed message interfaces, and FIX session sequence number management.
// Input FIX 4.4 message (SOH delimited, displayed with | for readability)
// 8=FIX.4.4|9=176|35=D|49=CLIENT|56=BROKER|34=215|52=20240315-10:00:00.000|
// 11=ORD-ABC123|21=1|55=AAPL|54=1|38=500|40=2|44=182.50|59=0|10=128|
// TypeScript interfaces for FIX messages
export enum Side { Buy = '1', Sell = '2', BuyMinus = '3', SellPlus = '4' }
export enum OrdType { Market = '1', Limit = '2', Stop = '3', StopLimit = '4' }
export enum TimeInForce { Day = '0', GTC = '1', OPG = '3', IOC = '4', FOK = '6' }
export enum HandlInst { Automated = '1', SemiAuto = '2', Manual = '3' }
export interface FixHeader {
beginString: string; // Tag 8 — FIX.4.4
bodyLength: number; // Tag 9
msgType: string; // Tag 35
senderCompId: string; // Tag 49
targetCompId: string; // Tag 56
msgSeqNum: number; // Tag 34
sendingTime: Date; // Tag 52
}
export interface NewOrderSingle {
header: FixHeader;
clOrdId: string; // Tag 11 — client order ID
handlInst: HandlInst; // Tag 21
symbol: string; // Tag 55
side: Side; // Tag 54
orderQty: bigint; // Tag 38 — use bigint for integer quantities
ordType: OrdType; // Tag 40
price?: string; // Tag 44 — string for exact decimal (only for Limit orders)
timeInForce: TimeInForce; // Tag 59
}
export interface ExecutionReport {
header: FixHeader;
orderId: string; // Tag 37
execId: string; // Tag 17
execType: string; // Tag 150
ordStatus: string; // Tag 39
symbol: string; // Tag 55
side: Side; // Tag 54
leavesQty: bigint; // Tag 151
cumQty: bigint; // Tag 14
avgPx: string; // Tag 6 — string for exact decimal
fills: FillReport[]; // Repeating group — Tags 382/375/376/377
}
// Repeating group for fills (NoContraBrokers = 382)
export interface FillReport {
contraBroker: string; // Tag 375
contraTrader: string; // Tag 377
fillQty: bigint; // Tag 376
}
// Full FIX parser with repeating group support
const SOH = '\x01';
// FIX tag dictionary (subset of FIX 4.4)
const TAG_MAP: Record<string, { field: string; type: 'string' | 'int' | 'decimal' | 'datetime' | 'boolean' }> = {
'8': { field: 'beginString', type: 'string' },
'9': { field: 'bodyLength', type: 'int' },
'34': { field: 'msgSeqNum', type: 'int' },
'35': { field: 'msgType', type: 'string' },
'49': { field: 'senderCompId', type: 'string' },
'52': { field: 'sendingTime', type: 'datetime' },
'56': { field: 'targetCompId', type: 'string' },
'6': { field: 'avgPx', type: 'decimal' },
'11': { field: 'clOrdId', type: 'string' },
'14': { field: 'cumQty', type: 'int' },
'17': { field: 'execId', type: 'string' },
'21': { field: 'handlInst', type: 'string' },
'37': { field: 'orderId', type: 'string' },
'38': { field: 'orderQty', type: 'int' },
'39': { field: 'ordStatus', type: 'string' },
'40': { field: 'ordType', type: 'string' },
'44': { field: 'price', type: 'decimal' },
'54': { field: 'side', type: 'string' },
'55': { field: 'symbol', type: 'string' },
'59': { field: 'timeInForce', type: 'string' },
'150': { field: 'execType', type: 'string' },
'151': { field: 'leavesQty', type: 'int' },
'375': { field: 'contraBroker', type: 'string' },
'376': { field: 'fillQty', type: 'int' },
'377': { field: 'contraTrader', type: 'string' },
'382': { field: 'noContraBrokers', type: 'int' }, // repeating group delimiter
};
// Repeating group definitions: delimiter tag → member tags
const REPEATING_GROUPS: Record<string, { listKey: string; memberTags: string[] }> = {
'382': { listKey: 'fills', memberTags: ['375', '376', '377'] },
'454': { listKey: 'securityAltId', memberTags: ['455', '456'] },
};
function parseFixMessage(rawMsg: string): Record<string, unknown> {
const pairs = rawMsg.split(SOH).filter(Boolean);
const result: Record<string, unknown> = {};
let activeGroup: { listKey: string; memberTags: string[]; current: Record<string, unknown> } | null = null;
let currentItems: Record<string, unknown>[] = [];
for (const pair of pairs) {
const eqIdx = pair.indexOf('=');
if (eqIdx === -1) continue;
const tag = pair.slice(0, eqIdx);
const raw = pair.slice(eqIdx + 1);
// Check if this tag starts a repeating group
const groupDef = REPEATING_GROUPS[tag];
if (groupDef) {
// Save previous group if active
if (activeGroup && currentItems.length > 0) {
result[activeGroup.listKey] = currentItems;
}
currentItems = [];
activeGroup = { ...groupDef, current: {} };
continue;
}
// Accumulate into current repeating group item
if (activeGroup?.memberTags.includes(tag)) {
if (Object.keys(activeGroup.current).length > 0 && tag === activeGroup.memberTags[0]) {
// New group item starts
currentItems.push(activeGroup.current);
activeGroup.current = {};
}
activeGroup.current[TAG_MAP[tag]?.field ?? tag] = castValue(tag, raw);
continue;
}
// Regular field
if (activeGroup && currentItems.length >= 0 && !activeGroup.memberTags.includes(tag)) {
if (Object.keys(activeGroup.current).length > 0) currentItems.push(activeGroup.current);
result[activeGroup.listKey] = currentItems;
activeGroup = null;
}
const def = TAG_MAP[tag];
result[def?.field ?? `tag_${tag}`] = castValue(tag, raw);
}
return result;
}
function castValue(tag: string, raw: string): unknown {
const def = TAG_MAP[tag];
if (!def) return raw;
switch (def.type) {
case 'int': return BigInt(raw);
case 'decimal': return raw; // keep as string — use Decimal.js for arithmetic
case 'datetime': return parseFIXDate(raw);
case 'boolean': return raw === 'Y';
default: return raw;
}
}
// FIX datetime format: YYYYMMDD-HH:MM:SS.sss
function parseFIXDate(fixDate: string): Date {
const [date, time] = fixDate.split('-');
return new Date(`${date.slice(0,4)}-${date.slice(4,6)}-${date.slice(6,8)}T${time}Z`);
}
// NEVER use JavaScript number for FIX price/quantity arithmetic
// 0.1 + 0.2 = 0.30000000000000004 in JavaScript
// Wrong — loses precision:
const price = parseFloat('182.50'); // OK for storage, NOT for arithmetic
const total = price * 500; // 91250 — happens to be exact here
const wrong = 0.1 + 0.2; // 0.30000000000000004
// Correct — use Decimal.js for all financial arithmetic
// npm install decimal.js
import Decimal from 'decimal.js';
function calcOrderValue(priceStr: string, qty: bigint): Decimal {
return new Decimal(priceStr).mul(qty.toString());
}
const orderValue = calcOrderValue('182.50', 500n);
console.log(orderValue.toFixed(2)); // "91250.00" — exact
// For tick checking (price must be a multiple of tick size):
function isValidTick(priceStr: string, tickSizeStr: string): boolean {
const price = new Decimal(priceStr);
const tickSize = new Decimal(tickSizeStr);
const remainder = price.mod(tickSize);
return remainder.isZero();
}
console.log(isValidTick('182.50', '0.01')); // true
console.log(isValidTick('182.555', '0.01')); // false
number introduces floating-point imprecision that causes regulatory compliance failures in trade reconciliation.Q: What FIX versions are in use today?
A: FIX 4.2 remains the most widely deployed version at broker-dealers (it's the "lingua franca" of equity trading). FIX 4.4 is standard for newer venues and multi-asset platforms. FIX 5.0 SP2 with FIXML is used for more complex derivatives workflows. Most market participants support 4.2 at minimum and 4.4 as preferred.
Q: Which npm package should I use for FIX parsing?
A: @fixio/fixparser supports FIX 4.0–5.0 SP2 with full data dictionary, session management, and TypeScript types. For lighter-weight needs, simple-fix-parser handles basic tag parsing. For HFT systems where JavaScript is too slow, the FIX engine stays in C++ and TypeScript only handles post-trade analytics.
Q: How do I build a FIX session over TCP in Node.js?
A: Use Node.js net module for the TCP socket. FIX sessions require sending Logon (35=A) after TCP connect, responding to Heartbeat (35=0) and TestRequest (35=1) messages, handling gap fill on resend requests, and sending Logout (35=5) on disconnect. The @fixio/fixparser library handles the full session state machine.
Is the processing local-only?
Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.
Can I use this for enterprise projects?
Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.
Why pasting proprietary company data into third-party web tools is a major liability, and how to stay safe.
A deep dive into combining Zod, React Query, and TypeScript for bulletproof API integration.
Code generation is just the beginning. Discover how a schema-first approach can eliminate 90% of your integration bugs.