@timmeck/trading-brain 2.31.57 → 2.31.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/ipc/router.d.ts +1 -0
  2. package/dist/ipc/router.js +2 -0
  3. package/dist/ipc/router.js.map +1 -1
  4. package/dist/trading-core.js +13 -1
  5. package/dist/trading-core.js.map +1 -1
  6. package/package.json +1 -1
  7. package/dist/ipc/__tests__/protocol.test.d.ts +0 -1
  8. package/dist/ipc/__tests__/protocol.test.js +0 -89
  9. package/dist/ipc/__tests__/protocol.test.js.map +0 -1
  10. package/dist/market/__tests__/market-data-service.test.d.ts +0 -1
  11. package/dist/market/__tests__/market-data-service.test.js +0 -231
  12. package/dist/market/__tests__/market-data-service.test.js.map +0 -1
  13. package/dist/market/__tests__/yahoo-provider.test.d.ts +0 -1
  14. package/dist/market/__tests__/yahoo-provider.test.js +0 -95
  15. package/dist/market/__tests__/yahoo-provider.test.js.map +0 -1
  16. package/dist/paper/__tests__/decision-engine.test.d.ts +0 -1
  17. package/dist/paper/__tests__/decision-engine.test.js +0 -131
  18. package/dist/paper/__tests__/decision-engine.test.js.map +0 -1
  19. package/dist/paper/__tests__/indicators.test.d.ts +0 -1
  20. package/dist/paper/__tests__/indicators.test.js +0 -126
  21. package/dist/paper/__tests__/indicators.test.js.map +0 -1
  22. package/dist/paper/__tests__/paper-engine.test.d.ts +0 -1
  23. package/dist/paper/__tests__/paper-engine.test.js +0 -128
  24. package/dist/paper/__tests__/paper-engine.test.js.map +0 -1
  25. package/dist/paper/__tests__/portfolio-optimizer.test.d.ts +0 -1
  26. package/dist/paper/__tests__/portfolio-optimizer.test.js +0 -103
  27. package/dist/paper/__tests__/portfolio-optimizer.test.js.map +0 -1
  28. package/dist/paper/__tests__/price-fetcher.test.d.ts +0 -1
  29. package/dist/paper/__tests__/price-fetcher.test.js +0 -133
  30. package/dist/paper/__tests__/price-fetcher.test.js.map +0 -1
  31. package/dist/signals/__tests__/fingerprint.test.d.ts +0 -1
  32. package/dist/signals/__tests__/fingerprint.test.js +0 -164
  33. package/dist/signals/__tests__/fingerprint.test.js.map +0 -1
  34. package/dist/signals/__tests__/wilson-score.test.d.ts +0 -1
  35. package/dist/signals/__tests__/wilson-score.test.js +0 -65
  36. package/dist/signals/__tests__/wilson-score.test.js.map +0 -1
  37. package/dist/utils/__tests__/hash.test.d.ts +0 -1
  38. package/dist/utils/__tests__/hash.test.js +0 -23
  39. package/dist/utils/__tests__/hash.test.js.map +0 -1
  40. package/dist/utils/__tests__/paths.test.d.ts +0 -1
  41. package/dist/utils/__tests__/paths.test.js +0 -60
  42. package/dist/utils/__tests__/paths.test.js.map +0 -1
@@ -1,95 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- vi.mock('../../utils/logger.js', () => ({
3
- getLogger: () => ({
4
- info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(),
5
- }),
6
- }));
7
- import { YahooProvider } from '../yahoo-provider.js';
8
- describe('YahooProvider', () => {
9
- let provider;
10
- beforeEach(() => {
11
- provider = new YahooProvider();
12
- vi.useFakeTimers();
13
- });
14
- afterEach(() => {
15
- vi.useRealTimers();
16
- vi.restoreAllMocks();
17
- });
18
- describe('stale price detection', () => {
19
- it('marks price as stale when timestamp is older than 15 minutes', async () => {
20
- const now = Date.now();
21
- // Timestamp 30 minutes ago (in seconds, as Yahoo returns)
22
- const staleTimestamp = Math.floor((now - 30 * 60 * 1000) / 1000);
23
- vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify({
24
- chart: {
25
- result: [{
26
- timestamp: [staleTimestamp],
27
- indicators: {
28
- quote: [{
29
- close: [150.50],
30
- open: [149.00],
31
- high: [151.00],
32
- low: [148.50],
33
- volume: [1000000],
34
- }],
35
- },
36
- }],
37
- },
38
- }), { status: 200 }));
39
- // Skip the delay
40
- vi.advanceTimersByTime(500);
41
- const prices = await provider.fetchPrices(['AAPL']);
42
- expect(prices.get('AAPL')).toBe(150.50);
43
- expect(provider.isStale('AAPL')).toBe(true);
44
- expect(provider.getStaleSymbols()).toContain('AAPL');
45
- });
46
- it('marks price as fresh when timestamp is recent', async () => {
47
- const now = Date.now();
48
- // Timestamp 5 minutes ago (fresh)
49
- const freshTimestamp = Math.floor((now - 5 * 60 * 1000) / 1000);
50
- vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify({
51
- chart: {
52
- result: [{
53
- timestamp: [freshTimestamp],
54
- indicators: {
55
- quote: [{
56
- close: [150.50],
57
- open: [149.00],
58
- high: [151.00],
59
- low: [148.50],
60
- volume: [1000000],
61
- }],
62
- },
63
- }],
64
- },
65
- }), { status: 200 }));
66
- vi.advanceTimersByTime(500);
67
- const prices = await provider.fetchPrices(['AAPL']);
68
- expect(prices.get('AAPL')).toBe(150.50);
69
- expect(provider.isStale('AAPL')).toBe(false);
70
- });
71
- it('clears stale flag when fresh price arrives', async () => {
72
- // First set as stale
73
- provider.staleSymbols.add('AAPL');
74
- expect(provider.isStale('AAPL')).toBe(true);
75
- const now = Date.now();
76
- const freshTimestamp = Math.floor((now - 2 * 60 * 1000) / 1000);
77
- vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify({
78
- chart: {
79
- result: [{
80
- timestamp: [freshTimestamp],
81
- indicators: {
82
- quote: [{
83
- close: [155.00],
84
- }],
85
- },
86
- }],
87
- },
88
- }), { status: 200 }));
89
- vi.advanceTimersByTime(500);
90
- await provider.fetchPrices(['AAPL']);
91
- expect(provider.isStale('AAPL')).toBe(false);
92
- });
93
- });
94
- });
95
- //# sourceMappingURL=yahoo-provider.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"yahoo-provider.test.js","sourceRoot":"","sources":["../../../src/market/__tests__/yahoo-provider.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,131 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { DecisionEngine } from '../decision-engine.js';
3
- const mockConfig = {
4
- enabled: true,
5
- intervalMs: 300_000,
6
- startingBalance: 10_000,
7
- maxPositionPct: 5,
8
- maxPositions: 10,
9
- stopLossPct: -2.5,
10
- takeProfitPct: 4,
11
- trailingStopActivation: 3,
12
- trailingStopDistance: 1.5,
13
- confidenceThreshold: 0.60,
14
- scoreThreshold: 80,
15
- timeExitHours: 24,
16
- cryptoIds: [],
17
- stockSymbols: [],
18
- };
19
- function createMockSignalService() {
20
- return {
21
- getConfidence: vi.fn().mockReturnValue(0),
22
- getSignalWeights: vi.fn().mockReturnValue({ rsi: 0.5, macd: 0.5 }),
23
- };
24
- }
25
- describe('DecisionEngine', () => {
26
- describe('Cold-Start Confidence', () => {
27
- it('should use 0.3 threshold when confidence is 0 (cold start)', () => {
28
- const signalService = createMockSignalService();
29
- signalService.getConfidence.mockReturnValue(0); // Cold start: no data
30
- const engine = new DecisionEngine(mockConfig, signalService);
31
- // With strong bullish divergence (RSI < 30 && MACD > 0), should still enter
32
- const indicators = {
33
- rsi14: 25, // Low RSI
34
- macd: { line: 0.5, signal: 0.3, histogram: 0.2 }, // Positive MACD
35
- trendScore: 1,
36
- volatility: 40,
37
- };
38
- const prices = new Map([['bitcoin', 50000]]);
39
- const indicatorMap = new Map([['bitcoin', indicators]]);
40
- const entries = engine.checkEntries(['bitcoin'], prices, indicatorMap, new Set());
41
- // Should find an entry because bullish divergence bypasses confidence threshold
42
- expect(entries.length).toBe(1);
43
- });
44
- it('should not enter in cold start without strong technical signal', () => {
45
- const signalService = createMockSignalService();
46
- signalService.getConfidence.mockReturnValue(0);
47
- const engine = new DecisionEngine(mockConfig, signalService);
48
- // Weak signals: RSI in neutral zone, no strong trend
49
- const indicators = {
50
- rsi14: 50,
51
- macd: { line: -0.1, signal: -0.2, histogram: 0.1 },
52
- trendScore: 0.5,
53
- volatility: 45,
54
- };
55
- const prices = new Map([['bitcoin', 50000]]);
56
- const indicatorMap = new Map([['bitcoin', indicators]]);
57
- const entries = engine.checkEntries(['bitcoin'], prices, indicatorMap, new Set());
58
- // Should NOT enter: no strong technical signal and confidence is 0
59
- expect(entries.length).toBe(0);
60
- });
61
- });
62
- describe('Exit conditions', () => {
63
- it('should trigger stop loss at -2.5%', () => {
64
- const signalService = createMockSignalService();
65
- const engine = new DecisionEngine(mockConfig, signalService);
66
- const position = {
67
- id: 1,
68
- symbol: 'bitcoin',
69
- side: 'long',
70
- entryPrice: 50000,
71
- quantity: 0.01,
72
- usdtAmount: 500,
73
- currentPrice: 48700,
74
- pnlPct: -2.6,
75
- highWaterMark: 50000,
76
- signalsJson: '{}',
77
- fingerprint: 'abc',
78
- confidence: 0.7,
79
- regime: 'bullish_trend',
80
- openedAt: new Date().toISOString(),
81
- };
82
- const prices = new Map([['bitcoin', 48700]]);
83
- const exits = engine.checkExits([position], prices);
84
- expect(exits.length).toBe(1);
85
- expect(exits[0].reason).toBe('stop_loss');
86
- });
87
- it('should trigger time exit after 24 hours', () => {
88
- const signalService = createMockSignalService();
89
- const engine = new DecisionEngine(mockConfig, signalService);
90
- const oldDate = new Date(Date.now() - 25 * 3600 * 1000).toISOString(); // 25 hours ago
91
- const position = {
92
- id: 1,
93
- symbol: 'bitcoin',
94
- side: 'long',
95
- entryPrice: 50000,
96
- quantity: 0.01,
97
- usdtAmount: 500,
98
- currentPrice: 50100,
99
- pnlPct: 0.2,
100
- highWaterMark: 50100,
101
- signalsJson: '{}',
102
- fingerprint: 'abc',
103
- confidence: 0.7,
104
- regime: 'bullish_trend',
105
- openedAt: oldDate,
106
- };
107
- const prices = new Map([['bitcoin', 50100]]);
108
- const exits = engine.checkExits([position], prices);
109
- expect(exits.length).toBe(1);
110
- expect(exits[0].reason).toBe('time_exit');
111
- });
112
- });
113
- describe('Regime detection', () => {
114
- it('should detect bullish trend', () => {
115
- const signalService = createMockSignalService();
116
- const engine = new DecisionEngine(mockConfig, signalService);
117
- expect(engine.detectRegime({ rsi14: 60, macd: { line: 1, signal: 0.5, histogram: 0.5 }, trendScore: 3, volatility: 30 })).toBe('bullish_trend');
118
- });
119
- it('should detect bearish trend', () => {
120
- const signalService = createMockSignalService();
121
- const engine = new DecisionEngine(mockConfig, signalService);
122
- expect(engine.detectRegime({ rsi14: 30, macd: { line: -1, signal: -0.5, histogram: -0.5 }, trendScore: -3, volatility: 30 })).toBe('bearish_trend');
123
- });
124
- it('should detect volatile market', () => {
125
- const signalService = createMockSignalService();
126
- const engine = new DecisionEngine(mockConfig, signalService);
127
- expect(engine.detectRegime({ rsi14: 50, macd: { line: 0, signal: 0, histogram: 0 }, trendScore: 0, volatility: 70 })).toBe('volatile');
128
- });
129
- });
130
- });
131
- //# sourceMappingURL=decision-engine.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decision-engine.test.js","sourceRoot":"","sources":["../../../src/paper/__tests__/decision-engine.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,126 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { calcRSI, calcMACD, calcTrendScore, calcVolatility, calcAllIndicators } from '../indicators.js';
3
- function makeCandles(closes, base = 100) {
4
- return closes.map((close, i) => ({
5
- timestamp: Date.now() - (closes.length - i) * 300_000,
6
- open: close - 0.5,
7
- high: close + 1,
8
- low: close - 1,
9
- close,
10
- volume: 1000,
11
- }));
12
- }
13
- describe('calcRSI', () => {
14
- it('returns 50 when not enough data', () => {
15
- expect(calcRSI(makeCandles([100, 101, 102]))).toBe(50);
16
- });
17
- it('returns high RSI for consistent uptrend', () => {
18
- const prices = Array.from({ length: 30 }, (_, i) => 100 + i);
19
- const rsi = calcRSI(makeCandles(prices));
20
- expect(rsi).toBeGreaterThan(70);
21
- });
22
- it('returns low RSI for consistent downtrend', () => {
23
- const prices = Array.from({ length: 30 }, (_, i) => 130 - i);
24
- const rsi = calcRSI(makeCandles(prices));
25
- expect(rsi).toBeLessThan(30);
26
- });
27
- it('returns ~50 for flat market', () => {
28
- const prices = Array.from({ length: 30 }, (_, i) => 100 + (i % 2 === 0 ? 0.5 : -0.5));
29
- const rsi = calcRSI(makeCandles(prices));
30
- expect(rsi).toBeGreaterThan(30);
31
- expect(rsi).toBeLessThan(70);
32
- });
33
- it('returns 100 when all gains', () => {
34
- const prices = Array.from({ length: 20 }, (_, i) => 100 + i * 2);
35
- const rsi = calcRSI(makeCandles(prices));
36
- expect(rsi).toBe(100);
37
- });
38
- });
39
- describe('calcMACD', () => {
40
- it('returns zeroes when not enough data', () => {
41
- const result = calcMACD(makeCandles([100, 101, 102]));
42
- expect(result.line).toBe(0);
43
- expect(result.signal).toBe(0);
44
- expect(result.histogram).toBe(0);
45
- });
46
- it('returns positive MACD for uptrend', () => {
47
- const prices = Array.from({ length: 50 }, (_, i) => 100 + i);
48
- const result = calcMACD(makeCandles(prices));
49
- expect(result.line).toBeGreaterThan(0);
50
- });
51
- it('returns negative MACD for downtrend', () => {
52
- const prices = Array.from({ length: 50 }, (_, i) => 150 - i);
53
- const result = calcMACD(makeCandles(prices));
54
- expect(result.line).toBeLessThan(0);
55
- });
56
- it('histogram equals line minus signal', () => {
57
- const prices = Array.from({ length: 50 }, (_, i) => 100 + Math.sin(i / 5) * 10);
58
- const result = calcMACD(makeCandles(prices));
59
- expect(result.histogram).toBeCloseTo(result.line - result.signal, 10);
60
- });
61
- });
62
- describe('calcTrendScore', () => {
63
- it('returns 0 when not enough data', () => {
64
- expect(calcTrendScore(makeCandles([100, 101]))).toBe(0);
65
- });
66
- it('returns positive score for uptrend', () => {
67
- const prices = Array.from({ length: 40 }, (_, i) => 100 + i * 2);
68
- const score = calcTrendScore(makeCandles(prices));
69
- expect(score).toBeGreaterThan(0);
70
- });
71
- it('returns negative score for downtrend', () => {
72
- const prices = Array.from({ length: 40 }, (_, i) => 200 - i * 2);
73
- const score = calcTrendScore(makeCandles(prices));
74
- expect(score).toBeLessThan(0);
75
- });
76
- it('clamps to -5..+5 range', () => {
77
- const prices = Array.from({ length: 40 }, (_, i) => 100 + i * 20);
78
- const score = calcTrendScore(makeCandles(prices));
79
- expect(score).toBeLessThanOrEqual(5);
80
- expect(score).toBeGreaterThanOrEqual(-5);
81
- });
82
- });
83
- describe('calcVolatility', () => {
84
- it('returns default when not enough data', () => {
85
- expect(calcVolatility(makeCandles([100, 101]))).toBe(30);
86
- });
87
- it('returns low volatility for stable prices', () => {
88
- const prices = Array.from({ length: 20 }, () => 100);
89
- const candles = prices.map((close, i) => ({
90
- timestamp: Date.now() - (prices.length - i) * 300_000,
91
- open: close,
92
- high: close + 0.1,
93
- low: close - 0.1,
94
- close,
95
- volume: 1000,
96
- }));
97
- const vol = calcVolatility(candles);
98
- expect(vol).toBeLessThan(5);
99
- });
100
- it('returns high volatility for wild swings', () => {
101
- const candles = Array.from({ length: 20 }, (_, i) => ({
102
- timestamp: Date.now() - (20 - i) * 300_000,
103
- open: 100,
104
- high: 120,
105
- low: 80,
106
- close: 100,
107
- volume: 1000,
108
- }));
109
- const vol = calcVolatility(candles);
110
- expect(vol).toBeGreaterThan(20);
111
- });
112
- });
113
- describe('calcAllIndicators', () => {
114
- it('returns all four indicators', () => {
115
- const prices = Array.from({ length: 50 }, (_, i) => 100 + i);
116
- const result = calcAllIndicators(makeCandles(prices));
117
- expect(result).toHaveProperty('rsi14');
118
- expect(result).toHaveProperty('macd');
119
- expect(result).toHaveProperty('trendScore');
120
- expect(result).toHaveProperty('volatility');
121
- expect(result.macd).toHaveProperty('line');
122
- expect(result.macd).toHaveProperty('signal');
123
- expect(result.macd).toHaveProperty('histogram');
124
- });
125
- });
126
- //# sourceMappingURL=indicators.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"indicators.test.js","sourceRoot":"","sources":["../../../src/paper/__tests__/indicators.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,128 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { PaperEngine } from '../paper-engine.js';
3
- // Minimal mock config
4
- const mockConfig = {
5
- enabled: true,
6
- intervalMs: 300_000,
7
- startingBalance: 10_000,
8
- maxPositionPct: 5,
9
- maxPositions: 10,
10
- stopLossPct: -2.5,
11
- takeProfitPct: 4,
12
- trailingStopActivation: 3,
13
- trailingStopDistance: 1.5,
14
- confidenceThreshold: 0.60,
15
- scoreThreshold: 80,
16
- timeExitHours: 24,
17
- cryptoIds: ['bitcoin'],
18
- stockSymbols: [],
19
- };
20
- function createMockRepo() {
21
- return {
22
- getBalance: vi.fn().mockReturnValue({ balance: 10000, equity: 10000 }),
23
- updateBalance: vi.fn(),
24
- getOpenPositions: vi.fn().mockReturnValue([]),
25
- createPosition: vi.fn().mockReturnValue(1),
26
- deletePosition: vi.fn(),
27
- countPositions: vi.fn().mockReturnValue(0),
28
- getPositionBySymbol: vi.fn(),
29
- updatePositionPrice: vi.fn(),
30
- createTrade: vi.fn().mockReturnValue(1),
31
- getRecentTrades: vi.fn().mockReturnValue([]),
32
- getWinRate: vi.fn().mockReturnValue({ rate: 0, total: 0 }),
33
- getTotalPnl: vi.fn().mockReturnValue(0),
34
- savePrices: vi.fn(),
35
- getRecentPrices: vi.fn().mockReturnValue([]),
36
- pruneOldPrices: vi.fn(),
37
- };
38
- }
39
- function createMockTradeService() {
40
- return {
41
- recordOutcome: vi.fn(),
42
- getConfidence: vi.fn().mockReturnValue(0),
43
- getSignalWeights: vi.fn().mockReturnValue({}),
44
- };
45
- }
46
- function createMockSignalService() {
47
- return {
48
- getConfidence: vi.fn().mockReturnValue(0),
49
- getSignalWeights: vi.fn().mockReturnValue({}),
50
- };
51
- }
52
- describe('PaperEngine', () => {
53
- describe('Balance/Equity correctness', () => {
54
- it('should use calcEquity (not balance) when updating balance on open position', async () => {
55
- const repo = createMockRepo();
56
- const tradeService = createMockTradeService();
57
- const signalService = createMockSignalService();
58
- // Simulate an existing open position with PnL
59
- const existingPosition = {
60
- id: 1,
61
- symbol: 'ethereum',
62
- side: 'long',
63
- entryPrice: 2000,
64
- quantity: 0.25,
65
- usdtAmount: 500,
66
- currentPrice: 2100,
67
- pnlPct: 5,
68
- highWaterMark: 2100,
69
- signalsJson: '{}',
70
- fingerprint: 'abc',
71
- confidence: 0.7,
72
- regime: 'bullish_trend',
73
- openedAt: new Date().toISOString(),
74
- };
75
- // After opening a new position, equity should include unrealized PnL from existing positions
76
- repo.getOpenPositions.mockReturnValue([existingPosition]);
77
- repo.getBalance.mockReturnValue({ balance: 9500, equity: 9525 }); // balance minus 500 position
78
- const engine = new PaperEngine(mockConfig, tradeService, signalService, repo);
79
- // Verify that updateBalance is called with different values for balance and equity
80
- // when positions have unrealized PnL
81
- // The PortfolioManager.calcEquity adds unrealized PnL to balance
82
- // existingPosition has 5% PnL on $500 = $25 unrealized
83
- // So equity = newBalance + 25
84
- // We can test the portfolio manager directly
85
- const { PortfolioManager } = await import('../portfolio-manager.js');
86
- const portfolio = new PortfolioManager(mockConfig, repo);
87
- const equity = portfolio.calcEquity(9000);
88
- // existingPosition: usdtAmount=500, pnlPct=5 → position value = 500 * 1.05 = 525
89
- // equity = balance + position value = 9000 + 525 = 9525
90
- expect(equity).toBe(9525);
91
- });
92
- });
93
- describe('Cycle overlap protection', () => {
94
- it('should skip cycle if previous cycle is still in progress', async () => {
95
- const repo = createMockRepo();
96
- const tradeService = createMockTradeService();
97
- const signalService = createMockSignalService();
98
- const engine = new PaperEngine(mockConfig, tradeService, signalService, repo);
99
- // Start a slow cycle by making fetchAll hang
100
- let resolveFirst;
101
- const firstCyclePromise = new Promise(r => { resolveFirst = r; });
102
- // Mock PriceFetcher.fetchAll to hang
103
- const originalFetchAll = engine.priceFetcher.fetchAll;
104
- let callCount = 0;
105
- engine.priceFetcher.fetchAll = vi.fn().mockImplementation(async () => {
106
- callCount++;
107
- if (callCount === 1) {
108
- await firstCyclePromise; // Hang on first call
109
- return new Map();
110
- }
111
- return new Map();
112
- });
113
- // Start first cycle (will hang)
114
- const cycle1 = engine.runCycle();
115
- // Try to start second cycle while first is running
116
- const result2 = await engine.runCycle();
117
- // Second cycle should be skipped
118
- expect(result2).toEqual({ entries: 0, exits: 0 });
119
- // Resolve first cycle
120
- resolveFirst();
121
- await cycle1;
122
- // After first cycle completes, a new cycle should work
123
- const result3 = await engine.runCycle();
124
- expect(result3).toBeDefined();
125
- });
126
- });
127
- });
128
- //# sourceMappingURL=paper-engine.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"paper-engine.test.js","sourceRoot":"","sources":["../../../src/paper/__tests__/paper-engine.test.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,103 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import Database from 'better-sqlite3';
3
- vi.mock('../../utils/logger.js', () => ({
4
- getLogger: () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }),
5
- }));
6
- import { PortfolioOptimizer } from '../portfolio-optimizer.js';
7
- describe('PortfolioOptimizer', () => {
8
- let db;
9
- beforeEach(() => { db = new Database(':memory:'); });
10
- afterEach(() => { db.close(); });
11
- it('creates with default config', () => {
12
- const opt = new PortfolioOptimizer(db);
13
- const history = opt.getHistory();
14
- expect(history).toHaveLength(0);
15
- });
16
- it('calculates position size based on Kelly criterion', () => {
17
- const opt = new PortfolioOptimizer(db);
18
- const rec = opt.calcPositionSize(10000, 'BTC', 0.6, 100, 50, []);
19
- expect(rec.symbol).toBe('BTC');
20
- expect(rec.recommendedSize).toBeGreaterThan(0);
21
- expect(rec.kellyPct).toBeGreaterThan(0);
22
- expect(rec.diversificationOk).toBe(true);
23
- });
24
- it('caps position at maxPositionPct', () => {
25
- const opt = new PortfolioOptimizer(db, { maxPositionPct: 5 });
26
- const rec = opt.calcPositionSize(10000, 'BTC', 0.9, 200, 50, []);
27
- expect(rec.recommendedSize).toBeLessThanOrEqual(500); // 5% of 10000
28
- });
29
- it('limits concentration in single asset', () => {
30
- const opt = new PortfolioOptimizer(db, { maxConcentrationPct: 20 });
31
- const existing = [
32
- { symbol: 'BTC', usdtAmount: 1500 }, // already 15% of 10000
33
- ];
34
- const rec = opt.calcPositionSize(10000, 'BTC', 0.7, 100, 50, existing);
35
- // Max 20% concentration = 2000 total, 1500 existing → max 500 more
36
- expect(rec.recommendedSize).toBeLessThanOrEqual(500);
37
- });
38
- it('returns 0 size when fully concentrated', () => {
39
- const opt = new PortfolioOptimizer(db, { maxConcentrationPct: 10 });
40
- const existing = [{ symbol: 'BTC', usdtAmount: 1000 }]; // already at 10%
41
- const rec = opt.calcPositionSize(10000, 'BTC', 0.7, 100, 50, existing);
42
- expect(rec.recommendedSize).toBe(0);
43
- });
44
- it('handles zero equity', () => {
45
- const opt = new PortfolioOptimizer(db);
46
- const rec = opt.calcPositionSize(0, 'BTC', 0.6, 100, 50, []);
47
- expect(rec.recommendedSize).toBe(0);
48
- });
49
- it('handles zero loss (no loss ratio)', () => {
50
- const opt = new PortfolioOptimizer(db);
51
- const rec = opt.calcPositionSize(10000, 'ETH', 0.6, 100, 0, []);
52
- expect(rec.recommendedSize).toBeGreaterThanOrEqual(0);
53
- });
54
- it('checks portfolio health with positions', () => {
55
- const opt = new PortfolioOptimizer(db);
56
- const health = opt.checkHealth(10000, [
57
- { symbol: 'BTC', usdtAmount: 2000 },
58
- { symbol: 'ETH', usdtAmount: 1500 },
59
- { symbol: 'SOL', usdtAmount: 1000 },
60
- ]);
61
- expect(health.positionCount).toBe(3);
62
- expect(health.largestPositionPct).toBe(20);
63
- expect(health.smallestPositionPct).toBe(10);
64
- expect(health.diversificationScore).toBeGreaterThan(0);
65
- expect(health.concentrationRisk).toBe('medium'); // 20% > 25%*0.7 = 17.5%
66
- });
67
- it('detects high concentration risk', () => {
68
- const opt = new PortfolioOptimizer(db, { maxConcentrationPct: 20 });
69
- const health = opt.checkHealth(10000, [
70
- { symbol: 'BTC', usdtAmount: 5000 }, // 50%!
71
- { symbol: 'ETH', usdtAmount: 500 },
72
- ]);
73
- expect(health.concentrationRisk).toBe('high');
74
- expect(health.recommendations.length).toBeGreaterThan(0);
75
- });
76
- it('warns about low diversification', () => {
77
- const opt = new PortfolioOptimizer(db, { minDiversification: 5 });
78
- const health = opt.checkHealth(10000, [
79
- { symbol: 'BTC', usdtAmount: 2000 },
80
- { symbol: 'ETH', usdtAmount: 2000 },
81
- ]);
82
- expect(health.recommendations.some(r => r.includes('diversification'))).toBe(true);
83
- });
84
- it('returns empty health for no positions', () => {
85
- const opt = new PortfolioOptimizer(db);
86
- const health = opt.checkHealth(10000, []);
87
- expect(health.positionCount).toBe(0);
88
- expect(health.concentrationRisk).toBe('low');
89
- });
90
- it('records snapshots', () => {
91
- const opt = new PortfolioOptimizer(db);
92
- opt.checkHealth(10000, [{ symbol: 'BTC', usdtAmount: 1000 }]);
93
- opt.checkHealth(12000, [{ symbol: 'BTC', usdtAmount: 1200 }]);
94
- const history = opt.getHistory();
95
- expect(history).toHaveLength(2);
96
- });
97
- it('needsRebalance detects drift', () => {
98
- const opt = new PortfolioOptimizer(db, { rebalanceThresholdPct: 5 });
99
- expect(opt.needsRebalance(15, 10)).toBe(true);
100
- expect(opt.needsRebalance(11, 10)).toBe(false);
101
- });
102
- });
103
- //# sourceMappingURL=portfolio-optimizer.test.js.map