157 lines
4.8 KiB
TypeScript
157 lines
4.8 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { mergeOrderSnapshots, mergePositionSnapshots } from '../src/services/stateMerge.js';
|
|
|
|
const testMergePositionSnapshots = () => {
|
|
const merged = mergePositionSnapshots([
|
|
[
|
|
{
|
|
id: 'p-global-1',
|
|
symbol: 'BTC/USD',
|
|
side: 'BUY',
|
|
size: 0.5,
|
|
entryPrice: 50000,
|
|
currentPrice: 50500,
|
|
stopLoss: 49000,
|
|
takeProfit: 52000,
|
|
unrealizedPnl: 250,
|
|
unrealizedPnlPercent: 1,
|
|
marketValue: 25250,
|
|
tradeId: 'TRD-abc-1'
|
|
}
|
|
],
|
|
[
|
|
{
|
|
id: 'p-profile-1',
|
|
symbol: 'BTC/USD',
|
|
side: 'BUY',
|
|
size: 0.5,
|
|
entryPrice: 50000,
|
|
currentPrice: 50600,
|
|
stopLoss: 49100,
|
|
takeProfit: 52100,
|
|
unrealizedPnl: 300,
|
|
unrealizedPnlPercent: 1.2,
|
|
marketValue: 25300,
|
|
userId: 'user-1',
|
|
profileId: 'profile-1',
|
|
profileName: 'High Risk',
|
|
tradeId: 'TRD-abc-1'
|
|
}
|
|
]
|
|
]);
|
|
|
|
assert.equal(merged.length, 1, 'stable trade_id duplicates should collapse');
|
|
assert.equal(merged[0].profileId, 'profile-1', 'profile should be preserved from richer position');
|
|
assert.equal(merged[0].userId, 'user-1', 'user should be preserved from richer position');
|
|
assert.equal(merged[0].tradeId, 'TRD-abc-1');
|
|
};
|
|
|
|
const testMergePositionFallbackReduction = () => {
|
|
const merged = mergePositionSnapshots([
|
|
[
|
|
{
|
|
id: 'fallback-1',
|
|
symbol: 'ETH/USD',
|
|
side: 'BUY',
|
|
size: 1,
|
|
entryPrice: 2000,
|
|
currentPrice: 2010,
|
|
stopLoss: 1900,
|
|
takeProfit: 2200,
|
|
unrealizedPnl: 10,
|
|
unrealizedPnlPercent: 0.5,
|
|
marketValue: 2010,
|
|
profileId: 'profile-1',
|
|
tradeId: 'TRD-SYNC-profile-1-ETHUSD'
|
|
},
|
|
{
|
|
id: 'fallback-2',
|
|
symbol: 'ETH/USD',
|
|
side: 'BUY',
|
|
size: 1,
|
|
entryPrice: 2000,
|
|
currentPrice: 2020,
|
|
stopLoss: 1890,
|
|
takeProfit: 2210,
|
|
unrealizedPnl: 20,
|
|
unrealizedPnlPercent: 1,
|
|
marketValue: 2020,
|
|
profileId: 'profile-1'
|
|
}
|
|
]
|
|
]);
|
|
|
|
assert.equal(merged.length, 1, 'fallback same owner+symbol+side should reduce to one row');
|
|
assert.equal(merged[0].profileId, 'profile-1');
|
|
assert.equal(merged[0].symbol, 'ETH/USD');
|
|
};
|
|
|
|
const testMergeOrderSnapshots = () => {
|
|
const merged = mergeOrderSnapshots([
|
|
[
|
|
{
|
|
id: 'ord-1',
|
|
symbol: 'BTC/USD',
|
|
type: 'Market',
|
|
side: 'BUY',
|
|
qty: 0.25,
|
|
price: 50000,
|
|
status: 'pending_new',
|
|
timestamp: 1000
|
|
}
|
|
],
|
|
[
|
|
{
|
|
id: 'ord-1',
|
|
symbol: 'BTC/USD',
|
|
type: 'Market',
|
|
side: 'BUY',
|
|
qty: 0.25,
|
|
price: 50010,
|
|
status: 'filled',
|
|
timestamp: 1050,
|
|
profileId: 'profile-2',
|
|
trade_id: 'TRD-ord-1',
|
|
action: 'ENTRY',
|
|
source: 'BOT'
|
|
},
|
|
{
|
|
id: 'ord-2',
|
|
symbol: 'ETH/USD',
|
|
type: 'Market',
|
|
side: 'SELL',
|
|
qty: 1,
|
|
price: 2000,
|
|
status: 'rejected',
|
|
timestamp: 900,
|
|
profileId: 'profile-2',
|
|
trade_id: 'TRD-ord-2',
|
|
action: 'EXIT',
|
|
source: 'BOT'
|
|
}
|
|
]
|
|
]);
|
|
|
|
assert.equal(merged.length, 2, 'orders should be merged by order id');
|
|
const ord1 = merged.find((order) => order.id === 'ord-1');
|
|
assert.ok(ord1, 'ord-1 should exist');
|
|
assert.equal(ord1?.status, 'filled', 'terminal status should not be downgraded');
|
|
assert.equal(ord1?.profileId, 'profile-2');
|
|
assert.equal(ord1?.trade_id, 'TRD-ord-1');
|
|
assert.equal(ord1?.source, 'BOT');
|
|
assert.equal(ord1?.action, 'ENTRY');
|
|
|
|
const ord2 = merged.find((order) => order.id === 'ord-2');
|
|
assert.equal(ord2?.status, 'rejected');
|
|
};
|
|
|
|
const run = () => {
|
|
testMergePositionSnapshots();
|
|
testMergePositionFallbackReduction();
|
|
testMergeOrderSnapshots();
|
|
console.log('[state-merge-coverage] OK: position/order snapshot merge behavior validated');
|
|
};
|
|
|
|
run();
|
|
|