- 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>
309 lines
11 KiB
TypeScript
309 lines
11 KiB
TypeScript
/**
|
|
* Decision Helpers Tests
|
|
*
|
|
* Tests for all decision helper functions in Position Manager:
|
|
* - shouldEmergencyStop() - LONG and SHORT
|
|
* - shouldStopLoss() - LONG and SHORT
|
|
* - shouldTakeProfit1() - LONG and SHORT
|
|
* - shouldTakeProfit2() - LONG and SHORT
|
|
*/
|
|
|
|
import {
|
|
createLongTrade,
|
|
createShortTrade,
|
|
TEST_DEFAULTS
|
|
} from '../../helpers/trade-factory'
|
|
|
|
describe('Decision Helpers', () => {
|
|
// Extract decision helpers from Position Manager
|
|
// These are pure functions that test price against targets
|
|
|
|
function shouldEmergencyStop(price: number, trade: { direction: 'long' | 'short', emergencyStopPrice: number }): boolean {
|
|
if (trade.direction === 'long') {
|
|
return price <= trade.emergencyStopPrice
|
|
} else {
|
|
return price >= trade.emergencyStopPrice
|
|
}
|
|
}
|
|
|
|
function shouldStopLoss(price: number, trade: { direction: 'long' | 'short', stopLossPrice: number }): boolean {
|
|
if (trade.direction === 'long') {
|
|
return price <= trade.stopLossPrice
|
|
} else {
|
|
return price >= trade.stopLossPrice
|
|
}
|
|
}
|
|
|
|
function shouldTakeProfit1(price: number, trade: { direction: 'long' | 'short', tp1Price: number }): boolean {
|
|
if (trade.direction === 'long') {
|
|
return price >= trade.tp1Price
|
|
} else {
|
|
return price <= trade.tp1Price
|
|
}
|
|
}
|
|
|
|
function shouldTakeProfit2(price: number, trade: { direction: 'long' | 'short', tp2Price: number }): boolean {
|
|
if (trade.direction === 'long') {
|
|
return price >= trade.tp2Price
|
|
} else {
|
|
return price <= trade.tp2Price
|
|
}
|
|
}
|
|
|
|
describe('shouldEmergencyStop()', () => {
|
|
describe('LONG positions', () => {
|
|
it('should trigger emergency stop when price at -2% (emergency level)', () => {
|
|
const trade = createLongTrade({ entryPrice: 140 })
|
|
// Emergency stop at -2% = $137.20
|
|
expect(trade.emergencyStopPrice).toBe(TEST_DEFAULTS.long.emergencySl)
|
|
|
|
expect(shouldEmergencyStop(137.20, trade)).toBe(true)
|
|
})
|
|
|
|
it('should trigger emergency stop when price below emergency level', () => {
|
|
const trade = createLongTrade()
|
|
|
|
expect(shouldEmergencyStop(136.00, trade)).toBe(true) // Below emergency
|
|
expect(shouldEmergencyStop(135.00, trade)).toBe(true) // Well below
|
|
})
|
|
|
|
it('should NOT trigger emergency stop above emergency level', () => {
|
|
const trade = createLongTrade()
|
|
|
|
expect(shouldEmergencyStop(138.00, trade)).toBe(false) // Above emergency but below SL
|
|
expect(shouldEmergencyStop(140.00, trade)).toBe(false) // At entry
|
|
expect(shouldEmergencyStop(141.00, trade)).toBe(false) // In profit
|
|
})
|
|
})
|
|
|
|
describe('SHORT positions', () => {
|
|
it('should trigger emergency stop when price at +2% (emergency level)', () => {
|
|
const trade = createShortTrade({ entryPrice: 140 })
|
|
// Emergency stop at +2% = $142.80
|
|
expect(trade.emergencyStopPrice).toBe(TEST_DEFAULTS.short.emergencySl)
|
|
|
|
expect(shouldEmergencyStop(142.80, trade)).toBe(true)
|
|
})
|
|
|
|
it('should trigger emergency stop when price above emergency level', () => {
|
|
const trade = createShortTrade()
|
|
|
|
expect(shouldEmergencyStop(143.00, trade)).toBe(true)
|
|
expect(shouldEmergencyStop(145.00, trade)).toBe(true)
|
|
})
|
|
|
|
it('should NOT trigger emergency stop below emergency level', () => {
|
|
const trade = createShortTrade()
|
|
|
|
expect(shouldEmergencyStop(142.00, trade)).toBe(false) // Below emergency but at SL
|
|
expect(shouldEmergencyStop(140.00, trade)).toBe(false) // At entry
|
|
expect(shouldEmergencyStop(138.00, trade)).toBe(false) // In profit
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('shouldStopLoss()', () => {
|
|
describe('LONG positions', () => {
|
|
it('should trigger SL when price at stop loss level', () => {
|
|
const trade = createLongTrade({ entryPrice: 140 })
|
|
// SL at -0.92% = $138.71
|
|
expect(trade.stopLossPrice).toBe(TEST_DEFAULTS.long.sl)
|
|
|
|
expect(shouldStopLoss(138.71, trade)).toBe(true)
|
|
})
|
|
|
|
it('should trigger SL when price below stop loss', () => {
|
|
const trade = createLongTrade()
|
|
|
|
expect(shouldStopLoss(138.00, trade)).toBe(true)
|
|
expect(shouldStopLoss(137.50, trade)).toBe(true)
|
|
})
|
|
|
|
it('should NOT trigger SL when price above stop loss', () => {
|
|
const trade = createLongTrade()
|
|
|
|
expect(shouldStopLoss(139.00, trade)).toBe(false)
|
|
expect(shouldStopLoss(140.00, trade)).toBe(false)
|
|
expect(shouldStopLoss(141.00, trade)).toBe(false)
|
|
})
|
|
|
|
it('should work with adjusted SL (breakeven after TP1)', () => {
|
|
const trade = createLongTrade({
|
|
entryPrice: 140,
|
|
stopLossPrice: 140.00, // Moved to breakeven
|
|
tp1Hit: true
|
|
})
|
|
|
|
expect(shouldStopLoss(139.99, trade)).toBe(true) // Just below breakeven
|
|
expect(shouldStopLoss(140.00, trade)).toBe(true) // At breakeven
|
|
expect(shouldStopLoss(140.01, trade)).toBe(false) // Above breakeven
|
|
})
|
|
})
|
|
|
|
describe('SHORT positions', () => {
|
|
it('should trigger SL when price at stop loss level', () => {
|
|
const trade = createShortTrade({ entryPrice: 140 })
|
|
// SL at +0.92% = $141.29
|
|
expect(trade.stopLossPrice).toBe(TEST_DEFAULTS.short.sl)
|
|
|
|
expect(shouldStopLoss(141.29, trade)).toBe(true)
|
|
})
|
|
|
|
it('should trigger SL when price above stop loss', () => {
|
|
const trade = createShortTrade()
|
|
|
|
expect(shouldStopLoss(142.00, trade)).toBe(true)
|
|
expect(shouldStopLoss(143.00, trade)).toBe(true)
|
|
})
|
|
|
|
it('should NOT trigger SL when price below stop loss', () => {
|
|
const trade = createShortTrade()
|
|
|
|
expect(shouldStopLoss(141.00, trade)).toBe(false)
|
|
expect(shouldStopLoss(140.00, trade)).toBe(false)
|
|
expect(shouldStopLoss(138.00, trade)).toBe(false)
|
|
})
|
|
|
|
it('should work with adjusted SL (breakeven after TP1)', () => {
|
|
const trade = createShortTrade({
|
|
entryPrice: 140,
|
|
stopLossPrice: 140.00, // Moved to breakeven
|
|
tp1Hit: true
|
|
})
|
|
|
|
expect(shouldStopLoss(140.01, trade)).toBe(true) // Just above breakeven
|
|
expect(shouldStopLoss(140.00, trade)).toBe(true) // At breakeven
|
|
expect(shouldStopLoss(139.99, trade)).toBe(false) // Below breakeven (in profit)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('shouldTakeProfit1()', () => {
|
|
describe('LONG positions', () => {
|
|
it('should trigger TP1 when price at target', () => {
|
|
const trade = createLongTrade({ entryPrice: 140 })
|
|
expect(trade.tp1Price).toBe(TEST_DEFAULTS.long.tp1)
|
|
|
|
expect(shouldTakeProfit1(141.20, trade)).toBe(true)
|
|
})
|
|
|
|
it('should trigger TP1 when price above target', () => {
|
|
const trade = createLongTrade()
|
|
|
|
expect(shouldTakeProfit1(141.50, trade)).toBe(true)
|
|
expect(shouldTakeProfit1(142.00, trade)).toBe(true)
|
|
})
|
|
|
|
it('should NOT trigger TP1 when price below target', () => {
|
|
const trade = createLongTrade()
|
|
|
|
expect(shouldTakeProfit1(141.00, trade)).toBe(false)
|
|
expect(shouldTakeProfit1(140.00, trade)).toBe(false)
|
|
expect(shouldTakeProfit1(139.00, trade)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('SHORT positions', () => {
|
|
it('should trigger TP1 when price at target', () => {
|
|
const trade = createShortTrade({ entryPrice: 140 })
|
|
expect(trade.tp1Price).toBe(TEST_DEFAULTS.short.tp1)
|
|
|
|
expect(shouldTakeProfit1(138.80, trade)).toBe(true)
|
|
})
|
|
|
|
it('should trigger TP1 when price below target (better for short)', () => {
|
|
const trade = createShortTrade()
|
|
|
|
expect(shouldTakeProfit1(138.50, trade)).toBe(true)
|
|
expect(shouldTakeProfit1(138.00, trade)).toBe(true)
|
|
})
|
|
|
|
it('should NOT trigger TP1 when price above target', () => {
|
|
const trade = createShortTrade()
|
|
|
|
expect(shouldTakeProfit1(139.00, trade)).toBe(false)
|
|
expect(shouldTakeProfit1(140.00, trade)).toBe(false)
|
|
expect(shouldTakeProfit1(141.00, trade)).toBe(false)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('shouldTakeProfit2()', () => {
|
|
describe('LONG positions', () => {
|
|
it('should trigger TP2 when price at target', () => {
|
|
const trade = createLongTrade({ entryPrice: 140 })
|
|
expect(trade.tp2Price).toBe(TEST_DEFAULTS.long.tp2)
|
|
|
|
expect(shouldTakeProfit2(142.41, trade)).toBe(true)
|
|
})
|
|
|
|
it('should trigger TP2 when price above target', () => {
|
|
const trade = createLongTrade()
|
|
|
|
expect(shouldTakeProfit2(143.00, trade)).toBe(true)
|
|
expect(shouldTakeProfit2(145.00, trade)).toBe(true)
|
|
})
|
|
|
|
it('should NOT trigger TP2 when price below target', () => {
|
|
const trade = createLongTrade()
|
|
|
|
expect(shouldTakeProfit2(142.00, trade)).toBe(false)
|
|
expect(shouldTakeProfit2(141.00, trade)).toBe(false)
|
|
expect(shouldTakeProfit2(140.00, trade)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('SHORT positions', () => {
|
|
it('should trigger TP2 when price at target', () => {
|
|
const trade = createShortTrade({ entryPrice: 140 })
|
|
expect(trade.tp2Price).toBe(TEST_DEFAULTS.short.tp2)
|
|
|
|
expect(shouldTakeProfit2(137.59, trade)).toBe(true)
|
|
})
|
|
|
|
it('should trigger TP2 when price below target (better for short)', () => {
|
|
const trade = createShortTrade()
|
|
|
|
expect(shouldTakeProfit2(137.00, trade)).toBe(true)
|
|
expect(shouldTakeProfit2(135.00, trade)).toBe(true)
|
|
})
|
|
|
|
it('should NOT trigger TP2 when price above target', () => {
|
|
const trade = createShortTrade()
|
|
|
|
expect(shouldTakeProfit2(138.00, trade)).toBe(false)
|
|
expect(shouldTakeProfit2(140.00, trade)).toBe(false)
|
|
expect(shouldTakeProfit2(141.00, trade)).toBe(false)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Decision order priority', () => {
|
|
it('emergency stop should trigger before regular SL', () => {
|
|
const trade = createLongTrade({ entryPrice: 140 })
|
|
const price = 136.00 // Below both emergency and SL
|
|
|
|
const emergencyTriggered = shouldEmergencyStop(price, trade)
|
|
const slTriggered = shouldStopLoss(price, trade)
|
|
|
|
expect(emergencyTriggered).toBe(true)
|
|
expect(slTriggered).toBe(true)
|
|
// In Position Manager, emergency is checked first (higher priority)
|
|
})
|
|
|
|
it('SL should NOT be checked if TP already hit the threshold', () => {
|
|
const trade = createLongTrade({ entryPrice: 140 })
|
|
const highPrice = 143.00 // Well above TP1 and TP2
|
|
|
|
const tp1Triggered = shouldTakeProfit1(highPrice, trade)
|
|
const tp2Triggered = shouldTakeProfit2(highPrice, trade)
|
|
const slTriggered = shouldStopLoss(highPrice, trade)
|
|
|
|
expect(tp1Triggered).toBe(true)
|
|
expect(tp2Triggered).toBe(true)
|
|
expect(slTriggered).toBe(false)
|
|
// When price is high, TP triggers but not SL
|
|
})
|
|
})
|
|
})
|