Files
trading_bot_v4/tests/integration/position-manager/price-verification.test.ts
copilot-swe-agent[bot] 1b6297b1e2 chore: Fix .gitignore - remove test file exclusions, add coverage folder
- Removed incorrect exclusion of *.test.ts and *.test.js files
- Added coverage/ folder to .gitignore
- Removed accidentally committed coverage files

Co-authored-by: mindesbunister <32161838+mindesbunister@users.noreply.github.com>
2025-12-05 00:16:50 +00:00

198 lines
7.2 KiB
TypeScript

/**
* 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)
})
})
})