Advanced Strategy Patterns
Advanced techniques and patterns for sophisticated trading strategies.
Overview
This guide covers advanced patterns used by experienced strategy developers:
- State machines for complex trade management
- Multi-timeframe analysis
- Dynamic position sizing
- Scaling in/out of positions
- Correlation-based filtering
State Machine Pattern
Manage complex trade lifecycles with explicit states:
// ============================================
// STATE MACHINE TRADING
// ============================================
const STATES = {
IDLE: 'idle',
SEEKING_ENTRY: 'seeking_entry',
ENTERING: 'entering',
IN_POSITION: 'in_position',
SCALING_OUT: 'scaling_out',
EXITING: 'exiting',
COOLDOWN: 'cooldown'
openGraph: { title: 'Advanced Patterns Example', description: 'Advanced custom strategy pattern examples.' },
};
const currentState = td.state.get('tradeState', STATES.IDLE);
function transitionTo(newState, data = {}) {
td.utils.log('State transition', {
from: currentState,
to: newState,
data: data
});
td.state.set('tradeState', newState);
td.state.set('stateEntryTime', Date.now());
td.state.set('stateData', data);
}
switch (currentState) {
case STATES.IDLE:
// Check for setup conditions
if (setupConditionMet()) {
transitionTo(STATES.SEEKING_ENTRY, { setupType: 'breakout' });
}
break;
case STATES.SEEKING_ENTRY:
// Look for precise entry timing
const stateData = td.state.get('stateData', {});
if (entryTrigger()) {
td.trade.buy({ amountPercent: 50, reason: 'Initial entry' });
transitionTo(STATES.ENTERING, { entryPrice: td.market.price });
} else if (setupInvalidated()) {
transitionTo(STATES.IDLE);
}
break;
case STATES.ENTERING:
// Confirm entry was filled
if (td.position.hasPosition) {
transitionTo(STATES.IN_POSITION, {
entryPrice: td.position.entryPrice,
positionSize: td.position.size
});
}
break;
case STATES.IN_POSITION:
// Manage position - scale out, adjust stops
if (td.position.pnlPercent > 3) {
td.trade.sell({ amount: td.position.size * 0.5 });
transitionTo(STATES.SCALING_OUT);
} else if (exitSignal()) {
transitionTo(STATES.EXITING);
}
break;
case STATES.SCALING_OUT:
// Continue managing remaining position
if (td.position.pnlPercent > 6 || exitSignal()) {
td.trade.close('Final exit');
transitionTo(STATES.COOLDOWN, { lastTradeTime: Date.now() });
}
break;
case STATES.EXITING:
if (!td.position.hasPosition) {
transitionTo(STATES.COOLDOWN, { lastTradeTime: Date.now() });
}
break;
case STATES.COOLDOWN:
const cooldownTime = 60 * 60 * 1000; // 1 hour
const lastTrade = td.state.get('stateData', {}).lastTradeTime || 0;
if (Date.now() - lastTrade > cooldownTime) {
transitionTo(STATES.IDLE);
}
break;
}
Dynamic Position Sizing
Adjust position size based on market conditions and strategy confidence:
// ============================================
// DYNAMIC POSITION SIZING
// ============================================
function calculatePositionSize() {
const baseSize = td.config.getNumber('BASE_POSITION_SIZE', 50);
// Factors that increase size
let multiplier = 1.0;
// 1. Confluence strength
const signalStrength = td.state.get('signalStrength', 3);
if (signalStrength >= 4) multiplier *= 1.3;
if (signalStrength >= 5) multiplier *= 1.2;
// 2. Volatility (lower vol = larger size)
const atr = td.indicators.atr;
const avgAtr = td.state.get('avgAtr', atr);
if (atr < avgAtr * 0.7) multiplier *= 1.2; // Low vol
// 3. Win streak (increase on success)
const consecutiveWins = td.state.get('consecutiveWins', 0);
if (consecutiveWins >= 2) multiplier *= 1.1;
if (consecutiveWins >= 4) multiplier *= 1.1;
// Factors that decrease size
// 1. High volatility
if (atr > avgAtr * 1.5) multiplier *= 0.7;
// 2. Losing streak
const consecutiveLosses = td.state.get('consecutiveLosses', 0);
if (consecutiveLosses >= 2) multiplier *= 0.7;
if (consecutiveLosses >= 4) multiplier *= 0.5;
// 3. Drawdown
const currentDrawdown = td.state.get('currentDrawdown', 0);
if (currentDrawdown > 5) multiplier *= 0.8;
if (currentDrawdown > 10) multiplier *= 0.6;
// Clamp final size
const finalSize = Math.min(Math.max(baseSize * multiplier, 10), 100);
td.utils.log('Position sizing', {
baseSize: baseSize,
multiplier: multiplier.toFixed(2),
finalSize: finalSize.toFixed(1)
});
return finalSize;
}
// Usage
if (shouldEnterTrade) {
td.trade.buy({
amountPercent: calculatePositionSize(),
reason: 'Dynamic sized entry'
});
}
Scaling In/Out Pattern
Build positions gradually and take profits incrementally:
// ============================================
// SCALE IN/OUT PATTERN
// ============================================
const MAX_SCALE_INS = td.config.getNumber('MAX_SCALE_INS', 3);
const SCALE_IN_PCT = td.config.getNumber('SCALE_IN_PCT', 33);
const SCALE_OUT_LEVELS = [2, 4, 6]; // Take profit at 2%, 4%, 6%
// SCALE IN LOGIC
if (!td.position.hasPosition || td.position.pnlPercent < 0) {
const scaleInCount = td.state.get('scaleInCount', 0);
if (scaleInCount < MAX_SCALE_INS && entryCondition) {
// Each scale-in requires stronger confirmation
const requiredConfluence = 2 + scaleInCount;
const currentConfluence = calculateConfluence();
if (currentConfluence >= requiredConfluence) {
td.trade.buy({
amountPercent: SCALE_IN_PCT,
reason: `Scale in ${scaleInCount + 1}/${MAX_SCALE_INS}`
});
td.state.set('scaleInCount', scaleInCount + 1);
td.state.set('lastScalePrice', td.market.price);
td.utils.log('SCALE IN', {
count: scaleInCount + 1,
confluence: currentConfluence,
price: td.market.price
});
}
}
}
// SCALE OUT LOGIC
if (td.position.hasPosition && td.position.side === 'long') {
const scalesOut = td.state.get('scalesOut', []);
for (let i = 0; i < SCALE_OUT_LEVELS.length; i++) {
const level = SCALE_OUT_LEVELS[i];
if (td.position.pnlPercent >= level && !scalesOut.includes(level)) {
// Take partial profit
const portionToClose = 1 / (SCALE_OUT_LEVELS.length - i);
const amountToClose = td.position.size * portionToClose;
td.trade.sell({
amount: amountToClose,
reason: `Scale out at ${level}% profit`
});
scalesOut.push(level);
td.state.set('scalesOut', scalesOut);
// Trail stop to break-even after first scale out
if (scalesOut.length === 1) {
td.trade.setStopLoss(td.position.entryPrice * 1.001);
}
td.utils.log('SCALE OUT', {
level: level,
amountClosed: amountToClose,
remaining: td.position.size - amountToClose
});
break;
}
}
}
// Reset on position close
if (!td.position.hasPosition) {
td.state.set('scaleInCount', 0);
td.state.set('scalesOut', []);
}
Time-based Filters
Filter trades based on time patterns:
// ============================================
// TIME-BASED FILTERS
// ============================================
function isGoodTradingTime() {
const now = new Date();
const hour = now.getUTCHours();
const dayOfWeek = now.getUTCDay(); // 0 = Sunday
// Skip weekends (crypto markets are open but less liquid)
if (dayOfWeek === 0 || dayOfWeek === 6) {
if (!td.config.getBoolean('TRADE_WEEKENDS', false)) {
return false;
}
}
// Best hours for crypto (overlap of major markets)
// London: 8-17 UTC, New York: 13-22 UTC
const goodHours = td.config.get('GOOD_HOURS', '8,9,10,11,12,13,14,15,16,17,18,19,20,21');
const allowedHours = goodHours.split(',').map(h => parseInt(h.trim()));
if (!allowedHours.includes(hour)) {
return false;
}
return true;
}
// Skip low-activity periods
function isHighActivityPeriod() {
const recentCandles = td.market.candles.slice(-10);
const avgVolume = recentCandles.reduce((s, c) => s + c.volume, 0) / 10;
const currentVolume = td.market.candles.slice(-1)[0].volume;
return currentVolume > avgVolume * 0.5;
}
// Usage
if (!isGoodTradingTime()) {
td.utils.log('Outside trading hours');
return;
}
if (!isHighActivityPeriod()) {
td.utils.log('Low activity period');
return;
}
Drawdown Protection
Implement portfolio-level risk management:
// ============================================
// DRAWDOWN PROTECTION
// ============================================
function updateDrawdownTracking() {
const balance = td.account.balance;
const peakBalance = td.state.get('peakBalance', balance);
const startOfDayBalance = td.state.get('startOfDayBalance', balance);
// Update peak
if (balance > peakBalance) {
td.state.set('peakBalance', balance);
}
// Calculate drawdowns
const totalDrawdown = ((peakBalance - balance) / peakBalance) * 100;
const dailyDrawdown = ((startOfDayBalance - balance) / startOfDayBalance) * 100;
td.state.set('totalDrawdown', totalDrawdown);
td.state.set('dailyDrawdown', dailyDrawdown);
return { totalDrawdown, dailyDrawdown openGraph: { title: 'Advanced Patterns Example', description: 'Advanced custom strategy pattern examples.' },
};
}
function checkDrawdownLimits() {
const { totalDrawdown, dailyDrawdown } = updateDrawdownTracking();
const maxTotalDrawdown = td.config.getNumber('MAX_TOTAL_DRAWDOWN', 15);
const maxDailyDrawdown = td.config.getNumber('MAX_DAILY_DRAWDOWN', 5);
if (totalDrawdown >= maxTotalDrawdown) {
td.utils.log('MAX TOTAL DRAWDOWN REACHED', {
drawdown: totalDrawdown.toFixed(2) + '%',
limit: maxTotalDrawdown + '%'
});
return false;
}
if (dailyDrawdown >= maxDailyDrawdown) {
td.utils.log('MAX DAILY DRAWDOWN REACHED', {
drawdown: dailyDrawdown.toFixed(2) + '%',
limit: maxDailyDrawdown + '%'
});
return false;
}
return true;
}
// Usage at strategy start
if (!checkDrawdownLimits()) {
// Close any open positions
if (td.position.hasPosition) {
td.trade.close('Drawdown limit reached');
}
return;
}
// Reset daily tracking at start of day
const lastResetDate = td.state.get('lastResetDate', '');
const today = new Date().toISOString().split('T')[0];
if (lastResetDate !== today) {
td.state.set('startOfDayBalance', td.account.balance);
td.state.set('lastResetDate', today);
}
Performance Tracking
Track strategy performance for optimization:
// ============================================
// PERFORMANCE TRACKING
// ============================================
function trackTradeResult() {
if (!td.position.hasPosition && td.state.get('wasInPosition', false)) {
// Position just closed
const trades = td.state.get('tradeHistory', []);
const lastEntry = td.state.get('lastEntryPrice', 0);
const pnl = td.state.get('lastPnl', 0);
trades.push({
entryPrice: lastEntry,
exitPrice: td.market.price,
pnl: pnl,
timestamp: Date.now(),
reason: td.state.get('exitReason', 'unknown')
});
// Keep last 100 trades
if (trades.length > 100) trades.shift();
td.state.set('tradeHistory', trades);
td.state.set('wasInPosition', false);
// Update win/loss tracking
if (pnl > 0) {
td.state.set('consecutiveWins', td.state.get('consecutiveWins', 0) + 1);
td.state.set('consecutiveLosses', 0);
} else {
td.state.set('consecutiveLosses', td.state.get('consecutiveLosses', 0) + 1);
td.state.set('consecutiveWins', 0);
}
// Calculate stats
calculatePerformanceStats(trades);
}
if (td.position.hasPosition) {
td.state.set('wasInPosition', true);
td.state.set('lastEntryPrice', td.position.entryPrice);
td.state.set('lastPnl', td.position.pnl);
}
}
function calculatePerformanceStats(trades) {
if (trades.length < 5) return;
const wins = trades.filter(t => t.pnl > 0);
const losses = trades.filter(t => t.pnl <= 0);
const stats = {
totalTrades: trades.length,
winRate: (wins.length / trades.length * 100).toFixed(1) + '%',
avgWin: wins.length > 0
? (wins.reduce((s, t) => s + t.pnl, 0) / wins.length).toFixed(2)
: 0,
avgLoss: losses.length > 0
? (losses.reduce((s, t) => s + t.pnl, 0) / losses.length).toFixed(2)
: 0,
profitFactor: losses.length > 0
? Math.abs(wins.reduce((s, t) => s + t.pnl, 0) /
losses.reduce((s, t) => s + t.pnl, 0)).toFixed(2)
: 'N/A'
openGraph: { title: 'Advanced Patterns Example', description: 'Advanced custom strategy pattern examples.' },
};
td.state.set('performanceStats', stats);
td.utils.log('Performance update', stats);
}
// Call at end of strategy
trackTradeResult();
Next Steps
- Best Practices - Essential guidelines
- Publishing - Share your strategies
- Testing & Validation - Test thoroughly