learning_ai_invt_trdg/backend/testManualTraderCapitalGuard.ts

103 lines
3.3 KiB
TypeScript

import assert from 'node:assert/strict';
import { ManualTrader } from '../src/services/ManualTrader.js';
import { SignalDirection } from '../src/strategies/rules/types.js';
import { supabaseService } from '../src/services/SupabaseService.js';
class MockExecutor {
public positions = new Map<string, any>();
public calls: Array<{ symbol: string; side: SignalDirection; qty: number; type: 'market' | 'limit'; price?: number }> = [];
getAllPositions() {
return this.positions;
}
getProfileId() {
return 'profile-cap-1';
}
getUserId() {
return 'user-cap-1';
}
getActivePosition(_symbol: string) {
return null;
}
async closePosition(_symbol: string, _reason: string) {
return { success: true };
}
async openPosition(
symbol: string,
side: SignalDirection,
qty: number,
type: 'market' | 'limit',
price?: number
) {
this.calls.push({ symbol, side, qty, type, price });
return { success: true, orderId: `mock-${Date.now()}` };
}
}
async function testScaleToRemainingCapital() {
const executor = new MockExecutor();
executor.positions.set('BTC/USD', {
entryPrice: 100,
size: 9 // committed = 900
});
const trader = new ManualTrader(executor as any);
const result = await trader.executeRequest('ETH/USD', 'buy', 2, 'market', undefined, 100, 'user-cap-1');
assert.equal(result.success, true, 'Manual trade should succeed when some capital remains.');
assert.equal(Number(result.adjustedQty), 1, 'Quantity should be reduced to remaining capital capacity.');
assert.equal(executor.calls.length, 1, 'openPosition should be called once.');
assert.equal(Number(executor.calls[0].qty), 1, 'Executor should receive scaled quantity.');
}
async function testWaitForCapitalRelease() {
const executor = new MockExecutor();
executor.positions.set('BTC/USD', {
entryPrice: 100,
size: 10 // committed = 1000, no immediate room
});
const trader = new ManualTrader(executor as any);
setTimeout(() => {
executor.positions.set('BTC/USD', {
entryPrice: 100,
size: 9 // remaining = 100 after release
});
}, 1200);
const start = Date.now();
const result = await trader.executeRequest('SOL/USD', 'buy', 1, 'market', undefined, 100, 'user-cap-1');
const elapsed = Date.now() - start;
assert.equal(result.success, true, 'Manual trade should proceed once capital is released.');
assert(elapsed >= 1000, 'Manual trader should wait for capital release window.');
assert.equal(executor.calls.length, 1, 'openPosition should execute after wait.');
assert.equal(Number(executor.calls[0].qty), 1, 'Released capital should allow requested quantity.');
}
async function run() {
const originalGetProfileCapital = (supabaseService as any).getProfileCapital;
(supabaseService as any).getProfileCapital = async () => ({
allocatedCapital: 1000,
isActive: true,
userId: 'user-cap-1'
});
try {
await testScaleToRemainingCapital();
await testWaitForCapitalRelease();
} finally {
(supabaseService as any).getProfileCapital = originalGetProfileCapital;
}
console.log('[manual-capital-guard] OK: scale-to-remaining and wait-for-release paths validated');
}
await run();