@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.
- package/dist/ipc/router.d.ts +1 -0
- package/dist/ipc/router.js +2 -0
- package/dist/ipc/router.js.map +1 -1
- package/dist/trading-core.js +13 -1
- package/dist/trading-core.js.map +1 -1
- package/package.json +1 -1
- package/dist/ipc/__tests__/protocol.test.d.ts +0 -1
- package/dist/ipc/__tests__/protocol.test.js +0 -89
- package/dist/ipc/__tests__/protocol.test.js.map +0 -1
- package/dist/market/__tests__/market-data-service.test.d.ts +0 -1
- package/dist/market/__tests__/market-data-service.test.js +0 -231
- package/dist/market/__tests__/market-data-service.test.js.map +0 -1
- package/dist/market/__tests__/yahoo-provider.test.d.ts +0 -1
- package/dist/market/__tests__/yahoo-provider.test.js +0 -95
- package/dist/market/__tests__/yahoo-provider.test.js.map +0 -1
- package/dist/paper/__tests__/decision-engine.test.d.ts +0 -1
- package/dist/paper/__tests__/decision-engine.test.js +0 -131
- package/dist/paper/__tests__/decision-engine.test.js.map +0 -1
- package/dist/paper/__tests__/indicators.test.d.ts +0 -1
- package/dist/paper/__tests__/indicators.test.js +0 -126
- package/dist/paper/__tests__/indicators.test.js.map +0 -1
- package/dist/paper/__tests__/paper-engine.test.d.ts +0 -1
- package/dist/paper/__tests__/paper-engine.test.js +0 -128
- package/dist/paper/__tests__/paper-engine.test.js.map +0 -1
- package/dist/paper/__tests__/portfolio-optimizer.test.d.ts +0 -1
- package/dist/paper/__tests__/portfolio-optimizer.test.js +0 -103
- package/dist/paper/__tests__/portfolio-optimizer.test.js.map +0 -1
- package/dist/paper/__tests__/price-fetcher.test.d.ts +0 -1
- package/dist/paper/__tests__/price-fetcher.test.js +0 -133
- package/dist/paper/__tests__/price-fetcher.test.js.map +0 -1
- package/dist/signals/__tests__/fingerprint.test.d.ts +0 -1
- package/dist/signals/__tests__/fingerprint.test.js +0 -164
- package/dist/signals/__tests__/fingerprint.test.js.map +0 -1
- package/dist/signals/__tests__/wilson-score.test.d.ts +0 -1
- package/dist/signals/__tests__/wilson-score.test.js +0 -65
- package/dist/signals/__tests__/wilson-score.test.js.map +0 -1
- package/dist/utils/__tests__/hash.test.d.ts +0 -1
- package/dist/utils/__tests__/hash.test.js +0 -23
- package/dist/utils/__tests__/hash.test.js.map +0 -1
- package/dist/utils/__tests__/paths.test.d.ts +0 -1
- package/dist/utils/__tests__/paths.test.js +0 -60
- 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
|