Skip to main content

Trade History

Every forex trade in StableFX follows a defined lifecycle from request-for-quote (RFQ) to final settlement. This page covers the ForexTradeRecord type, the ForexTradeStatus lifecycle, and how to fetch and display trade history.

Trade Lifecycle

StableFX trades progress through the following statuses:

  RFQ ──> QUOTED ──> MATCHED ──> SETTLED
| | |
| | +──> FAILED
| +──> FAILED
+──> FAILED
StatusDescription
RFQRequest-for-quote submitted. The system is sourcing prices from liquidity pools.
QUOTEDA quote has been received. The price is locked for a short window.
MATCHEDThe trade has been matched with a counterparty. Settlement is in progress.
SETTLEDTrade completed successfully. Funds have been distributed.
FAILEDTrade failed at any stage. Funds in the clearing pool are returned.

ForexTradeStatus Type

import type { ForexTradeStatus } from '@one_deploy/sdk';

type ForexTradeStatus = 'RFQ' | 'QUOTED' | 'MATCHED' | 'SETTLED' | 'FAILED';

ForexTradeRecord Type

import type { ForexTradeRecord } from '@one_deploy/sdk';

interface ForexTradeRecord {
/** Unique trade identifier. */
id: string;

/** The investment this trade belongs to. */
investmentId: string;

/** Currency pair traded, e.g. "EUR/USD". */
currencyPair: string;

/** Trade direction. */
side: 'buy' | 'sell';

/** Trade amount in base currency units. */
amount: number;

/** Quoted price at time of trade. */
quotePrice: number;

/** Execution price (may differ from quotePrice due to slippage). */
executionPrice: number | null;

/** Current trade status. */
status: ForexTradeStatus;

/** Realized profit or loss (in USDC), set after settlement. */
pnl: number | null;

/** Fee charged for this trade (in USDC). */
fee: number;

/** Slippage between quote and execution price (decimal). */
slippage: number | null;

/** ISO-8601 timestamp when the RFQ was submitted. */
createdAt: string;

/** ISO-8601 timestamp when the trade was last updated. */
updatedAt: string;

/** ISO-8601 timestamp when the trade was settled, if applicable. */
settledAt: string | null;

/** On-chain transaction hash, if applicable. */
txHash: string | null;

/** Reason for failure, if status is FAILED. */
failureReason: string | null;
}

Fetching Trade History

Trade records are accessed through the useForexInvestments hook's investment data, or directly through the useForexTrading hook.

Using useForexTrading

import { useForexTrading } from '@one_deploy/sdk';

function useTradeHistory() {
const { getTradeHistory } = useForexTrading();

const fetchTrades = async (investmentId: string) => {
const trades = await getTradeHistory({
investmentId,
limit: 50,
offset: 0,
});
return trades;
};

return { fetchTrades };
}

Full Trade History Screen

import React, { useEffect, useState } from 'react';
import { View, Text, FlatList, StyleSheet, ActivityIndicator } from 'react-native';
import { useForexTrading } from '@one_deploy/sdk';
import type { ForexTradeRecord, ForexTradeStatus } from '@one_deploy/sdk';

function TradeHistoryScreen({ investmentId }: { investmentId: string }) {
const { getTradeHistory } = useForexTrading();
const [trades, setTrades] = useState<ForexTradeRecord[]>([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
getTradeHistory({ investmentId, limit: 100 })
.then(setTrades)
.finally(() => setLoading(false));
}, [investmentId, getTradeHistory]);

if (loading) return <ActivityIndicator size="large" style={{ marginTop: 40 }} />;

return (
<FlatList
data={trades}
keyExtractor={(t) => t.id}
ListHeaderComponent={
<Text style={styles.header}>
Trade History ({trades.length} trades)
</Text>
}
renderItem={({ item }) => <TradeRow trade={item} />}
ListEmptyComponent={
<Text style={styles.empty}>No trades found.</Text>
}
/>
);
}

function TradeRow({ trade }: { trade: ForexTradeRecord }) {
const statusColor = getStatusColor(trade.status);

return (
<View style={styles.row}>
<View style={styles.rowLeft}>
<View style={styles.rowHeader}>
<Text style={styles.pair}>{trade.currencyPair}</Text>
<Text style={[styles.side, trade.side === 'buy' ? styles.buy : styles.sell]}>
{trade.side.toUpperCase()}
</Text>
</View>
<Text style={styles.amount}>
{trade.amount.toLocaleString()} units @ {trade.quotePrice}
</Text>
{trade.executionPrice && (
<Text style={styles.exec}>
Executed @ {trade.executionPrice}
{trade.slippage !== null &&
` (slippage: ${(trade.slippage * 100).toFixed(3)}%)`}
</Text>
)}
<Text style={styles.date}>
{new Date(trade.createdAt).toLocaleString()}
</Text>
</View>

<View style={styles.rowRight}>
<Text style={[styles.status, { color: statusColor }]}>
{trade.status}
</Text>
{trade.pnl !== null && (
<Text
style={[
styles.pnl,
{ color: trade.pnl >= 0 ? '#44cc88' : '#cc4444' },
]}
>
{trade.pnl >= 0 ? '+' : ''}${trade.pnl.toFixed(2)}
</Text>
)}
{trade.status === 'FAILED' && trade.failureReason && (
<Text style={styles.failReason}>{trade.failureReason}</Text>
)}
</View>
</View>
);
}

function getStatusColor(status: ForexTradeStatus): string {
switch (status) {
case 'RFQ': return '#ffaa44';
case 'QUOTED': return '#44aaff';
case 'MATCHED': return '#aa88ff';
case 'SETTLED': return '#44cc88';
case 'FAILED': return '#cc4444';
default: return '#888888';
}
}

const styles = StyleSheet.create({
header: { fontSize: 18, fontWeight: '700', color: '#fff', padding: 16 },
empty: { color: '#666', textAlign: 'center', padding: 40 },
row: {
flexDirection: 'row',
justifyContent: 'space-between',
padding: 14,
borderBottomWidth: 1,
borderColor: '#1a1a2e',
},
rowLeft: { flex: 1 },
rowRight: { alignItems: 'flex-end', justifyContent: 'center' },
rowHeader: { flexDirection: 'row', alignItems: 'center', gap: 8 },
pair: { fontSize: 15, fontWeight: '700', color: '#fff' },
side: { fontSize: 11, fontWeight: '700', paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4 },
buy: { backgroundColor: '#1a3a2a', color: '#44cc88' },
sell: { backgroundColor: '#3a1a1a', color: '#cc4444' },
amount: { fontSize: 13, color: '#aaa', marginTop: 4 },
exec: { fontSize: 12, color: '#888', marginTop: 2 },
date: { fontSize: 11, color: '#666', marginTop: 4 },
status: { fontSize: 12, fontWeight: '700' },
pnl: { fontSize: 14, fontWeight: '600', fontFamily: 'monospace', marginTop: 4 },
failReason: { fontSize: 11, color: '#cc4444', marginTop: 2, maxWidth: 120 },
});

Tracking Trade Status

You can poll for status updates on in-flight trades:

import { useForexTrading } from '@one_deploy/sdk';
import type { ForexTradeRecord } from '@one_deploy/sdk';

function useTradeStatusPolling(tradeId: string, intervalMs: number = 3000) {
const { getTradeById } = useForexTrading();
const [trade, setTrade] = useState<ForexTradeRecord | null>(null);

useEffect(() => {
let active = true;

const poll = async () => {
const updated = await getTradeById(tradeId);
if (!active) return;
setTrade(updated);

// Stop polling when trade reaches a terminal status
if (updated.status === 'SETTLED' || updated.status === 'FAILED') {
return;
}

setTimeout(poll, intervalMs);
};

poll();
return () => { active = false; };
}, [tradeId, intervalMs, getTradeById]);

return trade;
}

// Usage
function TradeStatusTracker({ tradeId }: { tradeId: string }) {
const trade = useTradeStatusPolling(tradeId);

if (!trade) return <Text>Loading...</Text>;

return (
<View style={{ padding: 16 }}>
<Text style={{ color: '#fff', fontSize: 16 }}>
Trade {trade.id.slice(0, 8)}...
</Text>
<Text style={{ color: getStatusColor(trade.status), marginTop: 8 }}>
Status: {trade.status}
</Text>
{trade.status === 'SETTLED' && (
<Text style={{ color: '#44cc88', marginTop: 4 }}>
Settled at {new Date(trade.settledAt!).toLocaleString()}
</Text>
)}
</View>
);
}

Trade Record Summary

FieldAvailable AtDescription
id, currencyPair, side, amountRFQSet when the trade is created
quotePriceQUOTEDSet when a quote is received
executionPrice, slippageMATCHEDSet when the trade is matched
pnl, settledAt, txHashSETTLEDSet upon successful settlement
failureReasonFAILEDSet when the trade fails

Next Steps