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