/** * Price Verification Tests * * Tests for price verification before setting TP flags. * * Key behaviors tested (Pitfall #43): * - Verify price reached TP1 before setting tp1Hit flag * - Require BOTH size reduced AND price at target * - Use tolerance for price target matching (0.2%) */ import { createLongTrade, createShortTrade, TEST_DEFAULTS } from '../../helpers/trade-factory' describe('Price Verification', () => { // Extract isPriceAtTarget logic from Position Manager function isPriceAtTarget(currentPrice: number, targetPrice: number, tolerance: number = 0.002): boolean { if (!targetPrice || targetPrice === 0) return false const diff = Math.abs(currentPrice - targetPrice) / targetPrice return diff <= tolerance } function shouldTakeProfit1(price: number, trade: { direction: 'long' | 'short', tp1Price: number }): boolean { if (trade.direction === 'long') { return price >= trade.tp1Price } else { return price <= trade.tp1Price } } describe('TP1 verification before setting flag (Pitfall #43)', () => { it('LONG: should require BOTH size reduction AND price at TP1', () => { const trade = createLongTrade({ entryPrice: 140, tp1Price: 141.20 }) // Scenario: Size reduced but price NOT at TP1 const sizeReduced = true const currentPrice = 140.50 // Below TP1 const priceAtTP1 = isPriceAtTarget(currentPrice, trade.tp1Price) // Should NOT set tp1Hit because price hasn't reached target const shouldSetTP1 = sizeReduced && priceAtTP1 expect(shouldSetTP1).toBe(false) // This was likely a MANUAL CLOSE, not TP1 }) it('LONG: should allow TP1 when both conditions met', () => { const trade = createLongTrade({ entryPrice: 140, tp1Price: 141.20 }) // Scenario: Size reduced AND price at TP1 const sizeReduced = true const currentPrice = 141.20 const priceAtTP1 = isPriceAtTarget(currentPrice, trade.tp1Price) const shouldSetTP1 = sizeReduced && priceAtTP1 expect(shouldSetTP1).toBe(true) }) it('SHORT: should require BOTH size reduction AND price at TP1', () => { const trade = createShortTrade({ entryPrice: 140, tp1Price: 138.80 }) // Scenario: Size reduced but price NOT at TP1 const sizeReduced = true const currentPrice = 139.50 // Above TP1 (short profits when price drops) const priceAtTP1 = isPriceAtTarget(currentPrice, trade.tp1Price) const shouldSetTP1 = sizeReduced && priceAtTP1 expect(shouldSetTP1).toBe(false) }) it('SHORT: should allow TP1 when both conditions met', () => { const trade = createShortTrade({ entryPrice: 140, tp1Price: 138.80 }) const sizeReduced = true const currentPrice = 138.80 const priceAtTP1 = isPriceAtTarget(currentPrice, trade.tp1Price) const shouldSetTP1 = sizeReduced && priceAtTP1 expect(shouldSetTP1).toBe(true) }) }) describe('isPriceAtTarget tolerance (0.2%)', () => { it('should return true when price exactly at target', () => { const result = isPriceAtTarget(141.20, 141.20) expect(result).toBe(true) }) it('should return true when price within 0.2% of target', () => { // 0.2% of 141.20 = 0.2824 // So prices from 140.92 to 141.48 should be "at target" expect(isPriceAtTarget(141.10, 141.20)).toBe(true) // -0.07% expect(isPriceAtTarget(141.30, 141.20)).toBe(true) // +0.07% expect(isPriceAtTarget(141.00, 141.20)).toBe(true) // -0.14% expect(isPriceAtTarget(141.40, 141.20)).toBe(true) // +0.14% }) it('should return false when price outside 0.2% tolerance', () => { // More than 0.2% away should NOT be "at target" expect(isPriceAtTarget(140.70, 141.20)).toBe(false) // -0.35% expect(isPriceAtTarget(141.60, 141.20)).toBe(false) // +0.28% expect(isPriceAtTarget(140.00, 141.20)).toBe(false) // -0.85% }) it('should handle edge case of 0 target price', () => { const result = isPriceAtTarget(141.20, 0) expect(result).toBe(false) }) it('should handle custom tolerance', () => { // Use stricter 0.1% tolerance expect(isPriceAtTarget(141.10, 141.20, 0.001)).toBe(true) // 0.07% < 0.1% expect(isPriceAtTarget(141.00, 141.20, 0.001)).toBe(false) // 0.14% > 0.1% // Use looser 0.5% tolerance expect(isPriceAtTarget(140.50, 141.20, 0.005)).toBe(true) // 0.5% = 0.5% expect(isPriceAtTarget(140.00, 141.20, 0.005)).toBe(false) // 0.85% > 0.5% }) }) describe('shouldTakeProfit1 with price verification', () => { it('LONG: combined size + price verification', () => { const trade = createLongTrade({ entryPrice: 140, tp1Price: 141.20, positionSize: 8000 }) // Simulate Position Manager logic const originalSize = trade.positionSize const currentSize = 3200 // After 60% close const sizeMismatch = currentSize < originalSize * 0.9 // Case 1: Price at TP1 const priceAtTP1 = 141.20 const priceReachedTP1 = shouldTakeProfit1(priceAtTP1, trade) expect(sizeMismatch && priceReachedTP1).toBe(true) // Valid TP1 // Case 2: Price NOT at TP1 const priceNotAtTP1 = 140.50 const priceReachedTP1_2 = shouldTakeProfit1(priceNotAtTP1, trade) expect(sizeMismatch && priceReachedTP1_2).toBe(false) // Invalid - manual close }) it('SHORT: combined size + price verification', () => { const trade = createShortTrade({ entryPrice: 140, tp1Price: 138.80, positionSize: 8000 }) const originalSize = trade.positionSize const currentSize = 3200 const sizeMismatch = currentSize < originalSize * 0.9 // Case 1: Price at TP1 (below entry for short) const priceAtTP1 = 138.80 const priceReachedTP1 = shouldTakeProfit1(priceAtTP1, trade) expect(sizeMismatch && priceReachedTP1).toBe(true) // Case 2: Price NOT at TP1 (still above target) const priceNotAtTP1 = 139.50 const priceReachedTP1_2 = shouldTakeProfit1(priceNotAtTP1, trade) expect(sizeMismatch && priceReachedTP1_2).toBe(false) }) }) describe('TP2 price verification', () => { function shouldTakeProfit2(price: number, trade: { direction: 'long' | 'short', tp2Price: number }): boolean { if (trade.direction === 'long') { return price >= trade.tp2Price } else { return price <= trade.tp2Price } } it('LONG: should detect TP2 at correct price', () => { const trade = createLongTrade({ tp2Price: 142.41 }) expect(shouldTakeProfit2(142.00, trade)).toBe(false) // Below expect(shouldTakeProfit2(142.41, trade)).toBe(true) // At target expect(shouldTakeProfit2(143.00, trade)).toBe(true) // Above }) it('SHORT: should detect TP2 at correct price', () => { const trade = createShortTrade({ tp2Price: 137.59 }) expect(shouldTakeProfit2(138.00, trade)).toBe(false) // Above (bad for short) expect(shouldTakeProfit2(137.59, trade)).toBe(true) // At target expect(shouldTakeProfit2(137.00, trade)).toBe(true) // Below (better for short) }) }) })