BDPO Library Documentation

Comprehensive guide to building trading strategies and indicators with JavaScript/TypeScript

Welcome to BDPO

BDPO is a powerful trading and investment analysis framework that allows you to create, test, and deploy trading strategies using JavaScript/TypeScript. Build class-based indicators, backtest strategies, and analyze real broker data with ease.

1

Create Class-Based Scripts

Write indicators and strategies as classes with lifecycle methods

2

Define Public Variables

Set user-configurable parameters that control script behavior

3

Backtest & Optimize

Test strategies on historical data and optimize parameters

4

Deploy Live

Forward test and run scenarios with real broker data

Core Features

Technical Analysis

Comprehensive library of technical indicators including moving averages, oscillators, momentum indicators, and custom calculations.

Strategy Development

Build automated trading strategies with entry/exit signals, risk management, and position sizing logic.

Data Visualization

Advanced plotting capabilities to visualize indicators, signals, and custom overlays on price charts.

Backtesting Engine

Test strategies against historical data with realistic execution simulation and performance metrics.

Portfolio Management

Track positions, calculate performance metrics, and manage multiple instruments simultaneously.

Broker Integration

Connect to real broker data feeds for live trading and realistic market simulation.

Getting Started: Begin with the Quick Start guide to create your first indicator or strategy. Check out the examples section for practical implementations.

Library Structure

The BDPO library is organized into focused modules, each providing specific functionality:

BDPO Library Structure:
├── technical_indicators    # 50+ technical analysis indicators
├── trade_functions         # Order execution and trade management
├── plotting_functions      # Chart visualization and overlays
├── position_functions      # Position tracking and management
├── account_functions       # Account and portfolio metrics
├── candlestick_functions   # OHLCV data manipulation
├── symbol_functions        # Market data and symbol utilities
├── math_functions          # Mathematical utilities and statistics
└── console_functions       # Debugging and logging tools

Quick Start Guide

Get up and running with BDPO in minutes

Your First Indicator

Let's create a simple moving average crossover indicator using the class-based structure:

// Simple Moving Average Crossover Indicator
class MovingAverageCrossover {
    // Public variables - users can modify these
    public fastPeriod: number = 10;
    public slowPeriod: number = 20;
    public lineColor: string = 'blue';
    public signalColor: string = 'red';
    
    // Private variables - internal use only
    private _plot: any = null;
    private _lastCrossover: string | null = null;

    __init__(): void {
        // Initialize plot builder and any one-time setup
        this._plot = new PlotBuilder();
        this._lastCrossover = null;
        
        console.log(`MA Crossover initialized: Fast=${this.fastPeriod}, Slow=${this.slowPeriod}`);
    }

    __tick__(): any {
        // Get price data
        const prices: number[] = bdpo.getClosePrices();
        
        // Calculate moving averages
        const fastMA: number[] = bdpo.sma(prices, this.fastPeriod);
        const slowMA: number[] = bdpo.sma(prices, this.slowPeriod);
        
        // Plot the moving averages
        this._plot.plot('splines', fastMA, {splineColor: this.lineColor, splineWidth: 2}, {name: `Fast MA (${this.fastPeriod})`});
        this._plot.plot('splines', slowMA, {splineColor: this.signalColor, splineWidth: 2}, {name: `Slow MA (${this.slowPeriod})`});
        
        // Generate signals
        const signals: any[] = [];
        const currentIndex: number = fastMA.length - 1;
        const prevIndex: number = currentIndex - 1;
        
        if (currentIndex > 0) {
            const currentFast: number = fastMA[currentIndex];
            const currentSlow: number = slowMA[currentIndex];
            const prevFast: number = fastMA[prevIndex];
            const prevSlow: number = slowMA[prevIndex];
            
            if (currentFast > currentSlow && prevFast <= prevSlow) {
                signals.push({type: 'BUY', price: prices[currentIndex], index: currentIndex});
                this._lastCrossover = 'bullish';
            } else if (currentFast < currentSlow && prevFast >= prevSlow) {
                signals.push({type: 'SELL', price: prices[currentIndex], index: currentIndex});
                this._lastCrossover = 'bearish';
            }
        }
        
        return {
            plots: this._plot.getMultiOverlayResponse(),
            signals: signals,
            lastCrossover: this._lastCrossover
        };
    }

    __shutdown__(): void {
        // Cleanup resources
        this._plot.clear();
        console.log('MA Crossover indicator shutdown');
    }
}

Your First Strategy

Now let's build a complete trading strategy using the class-based structure:

// Moving Average Crossover Trading Strategy
class MACrossoverStrategy {
    // Public variables - users can configure these
    public fastPeriod: number = 10;
    public slowPeriod: number = 20;
    public riskPercent: number = 2;        // Risk percentage per trade
    public stopLossPercent: number = 2;    // Stop loss percentage
    public takeProfitPercent: number = 6;  // Take profit percentage
    public maxPositions: number = 1;       // Maximum concurrent positions
    
    // Private variables - internal state management
    private _positions: any[] = [];
    private _lastSignal: string | null = null;
    private _initialized: boolean = false;

    __init__(): void {
        // Initialize strategy state
        this._positions = [];
        this._lastSignal = null;
        this._initialized = true;
        
        console.log(`MAC Strategy initialized: Fast=${this.fastPeriod}, Slow=${this.slowPeriod}, Risk=${this.riskPercent}%`);
    }

    __tick__(): any {
        if (!this._initialized) return null;
        
        const prices: number[] = bdpo.getClosePrices();
        const currentPrice: number = prices[prices.length - 1];
        
        // Calculate indicators
        const fastMA: number[] = bdpo.sma(prices, this.fastPeriod);
        const slowMA: number[] = bdpo.sma(prices, this.slowPeriod);
        
        const currentFast: number = fastMA[fastMA.length - 1];
        const currentSlow: number = slowMA[slowMA.length - 1];
        const prevFast: number = fastMA[fastMA.length - 2];
        const prevSlow: number = slowMA[slowMA.length - 2];
        
        // Calculate position size based on account risk
        const accountValue: number = bdpo.getAccountValue();
        const positionSize: number = (accountValue * this.riskPercent / 100) / currentPrice;
        
        // Trading logic
        const activePositions: any[] = this._positions.filter(pos => pos.status === 'open');
        
        if (activePositions.length < this.maxPositions) {
            // Look for entry signals
            if (currentFast > currentSlow && prevFast <= prevSlow) {
                // Bullish crossover - go long
                const position: any = bdpo.buy(positionSize, {
                    stopLoss: currentPrice * (1 - this.stopLossPercent / 100),
                    takeProfit: currentPrice * (1 + this.takeProfitPercent / 100),
                    notes: "MAC Bullish Crossover Entry"
                });
                
                if (position) {
                    this._positions.push(position);
                    this._lastSignal = 'buy';
                }
            }
        } else {
            // Manage existing positions
            if (currentFast < currentSlow && prevFast >= prevSlow) {
                // Bearish crossover - exit long positions
                activePositions.forEach((position: any) => {
                    if (position.side === 'LONG') {
                        bdpo.closePosition(position.id);
                        position.status = 'closed';
                        this._lastSignal = 'sell';
                    }
                });
            }
        }
        
        return {
            indicators: {
                fastMA: currentFast,
                slowMA: currentSlow,
                currentPrice: currentPrice
            },
            positions: this._positions,
            lastSignal: this._lastSignal,
            activePositions: activePositions.length
        };
    }

    __shutdown__(): void {
        // Close all open positions before shutdown
        const activePositions: any[] = this._positions.filter(pos => pos.status === 'open');
        
        activePositions.forEach((position: any) => {
            bdpo.closePosition(position.id);
            console.log(`Closed position ${position.id} during strategy shutdown`);
        });
        
        console.log(`MAC Strategy shutdown. Total positions: ${this._positions.length}`);
    }
}
Next Steps: Explore the individual modules to learn about advanced features like custom indicators, risk management, and portfolio optimization. Remember that all scripts must follow the class-based structure with __init__, __tick__, and __shutdown__ methods.

Architecture Overview

Understanding the BDPO framework structure and execution model

Execution Model

BDPO scripts follow a class-based lifecycle execution model designed for real-time trading applications:

Class-Based Script Lifecycle
All indicators and strategies are implemented as classes with three mandatory lifecycle methods
  1. Initialization: __init__() method called once when script loads
  2. Data Processing: __tick__() method called for each new market data point
  3. Cleanup: __shutdown__() method called when script is stopped or removed

Required Class Structure

class MyStrategy {
    // Define public variables (user-configurable)
    public fastPeriod: number = 10;     // Public - users can modify
    public slowPeriod: number = 20;     // Public - users can modify
    
    // Define private variables (internal use only)
    private _position: any = null;    // Private - users cannot modify
    private _lastSignal: string | null = null;  // Private - internal state

    __init__(): void {
        // Called once when strategy starts
        // Initialize indicators, reset state, etc.
    }

    __tick__(): any {
        // Called for every new data point/tick
        // Main strategy logic goes here
        // Return results for plotting/execution
    }

    __shutdown__(): void {
        // Called when strategy is stopped
        // Cleanup resources, close positions, etc.
    }
}

Public vs Private Variables

Variable Visibility
Control which parameters users can modify through proper variable naming
Variable Type Naming Convention User Access Example
Public No prefix, explicit public keyword ✅ User can modify public period: number = 20
Private Underscore prefix ❌ Internal use only private _cache: any[] = []
Best Practice: Always use explicit public and private decorators for all class variables. Use public variables for user-configurable parameters like periods, thresholds, and colors. Use private variables (with underscore prefix) for internal state, cached calculations, and temporary data.

Data Flow

Market Data → BDPO Core → Class Instance → Output Processing → Chart/Backtest
     ↓              ↓            ↓              ↓               ↓
  OHLCV Data   Data Validation  __tick__()    Format Results  Display/Execute
  Timestamps   Symbol Info      Indicators    Plot Data       Trade Orders
  Volume       Account State    Signals       Positions       Performance

Global Context

BDPO provides a global context object (self.bdpo) with access to market data and system state:

// Accessing global BDPO context
const symbol: string = self.bdpo.getSymbol();        // Current trading symbol
const timeframe: string = self.bdpo.getTimeframe();  // Chart timeframe
const data: number[][] = self.bdpo.data["0"];        // OHLCV data array
const source: string = self.bdpo.getSource();        // Data source/broker

// Data structure: [timestamp, open, high, low, close, volume]
const latestCandle: number[] = data[data.length - 1];
const currentPrice: number = latestCandle[4]; // Close price
Performance Tip: Cache expensive calculations in the __init__() method and store them in private variables. Only recalculate when necessary in __tick__() to optimize execution speed.

Technical Indicators

50+ technical analysis indicators powered by the industry-standard indicators-js library

Quick Reference: All indicators are accessed via bdpo.functionName(). Click any indicator below to see usage examples and parameters.

Moving Averages

SMA, EMA, WMA, DEMA, TEMA, HMA, KAMA, VWMA, ALMA

Oscillators

RSI, MACD, Stochastic, CCI, Williams %R, MFI, CMO

Bands & Channels

Bollinger Bands, Keltner Channels, Donchian Channels

Volume Indicators

OBV, VWAP, A/D Line, CMF, Force Index, Klinger

Volatility Indicators

ATR, True Range, Chaikin Volatility, VHF

Trend Indicators

Parabolic SAR, DMI, DX, TRIX, Linear Regression

Moving Averages

Smoothing indicators that reduce price noise and reveal trends.

bdpo.sma() Popular

Simple Moving Average - Equal weight to all periods

bdpo.ema() Popular

Exponential Moving Average - More weight to recent prices

bdpo.wma()

Weighted Moving Average - Linear weight distribution

bdpo.hma()

Hull Moving Average - Reduced lag, increased smoothness

bdpo.kama()

Kaufman Adaptive MA - Adapts to market volatility

bdpo.vwma()

Volume Weighted MA - Incorporates volume data

bdpo.sma(data, period)
Simple Moving Average - Equal weight to all periods in the calculation

Parameters

Parameter Type Description Example
data Array Price data array (typically close prices) data.close
period Number Number of periods to average 20, 50, 200

Usage Example

// Calculate 20-period SMA
private sma20: number[] = bdpo.sma(data.close, 20);
private currentSMA: number = this.sma20[this.sma20.length - 1];

// Multiple SMAs for crossover strategy
private smaFast: number[] = bdpo.sma(data.close, 10);
private smaSlow: number[] = bdpo.sma(data.close, 20);
bdpo.ema(data, period)
Exponential Moving Average - More weight to recent prices, faster response

Parameters

Parameter Type Description Example
data Array Price data array data.close
period Number Smoothing period 12, 26, 50

Usage Example

// MACD components using EMAs
private ema12: number[] = bdpo.ema(data.close, 12);
private ema26: number[] = bdpo.ema(data.close, 26);

// Check for bullish crossover
if (this.ema12[this.ema12.length - 1] > this.ema26[this.ema26.length - 1]) {
    // Fast EMA above slow EMA - bullish signal
}
Additional Moving Averages
Advanced moving averages for specialized analysis

bdpo.dema()

Double EMA - Reduces lag compared to standard EMA

bdpo.dema(data.close, 21)

bdpo.tema()

Triple EMA - Further lag reduction for fast signals

bdpo.tema(data.close, 14)

bdpo.trima()

Triangular MA - Double smoothed for stable trends

bdpo.trima(data.close, 20)

bdpo.zlema()

Zero-Lag EMA - Eliminates lag through prediction

bdpo.zlema(data.close, 20)

bdpo.alma()

Arnaud Legoux MA - Advanced adaptive smoothing

bdpo.alma(data.close, 21, 0.85, 6)

bdpo.vidya()

Variable Index Dynamic Average - Volatility adaptive

bdpo.vidya(data.close, 14)
bdpo.wma(data, period)
Weighted Moving Average - linear weights favor recent prices

Parameters

Parameter Type Description
data Array Price data array
period Number Number of periods to average

Example Usage

private wma20: number[] = bdpo.wma(data.close, 20);
private currentWMA: number = this.wma20[this.wma20.length - 1];

// WMA responds faster to price changes than SMA
private priceAboveWMA: boolean = data.close[data.close.length - 1] > this.currentWMA;
bdpo.dema(data, period)
Double Exponential Moving Average - reduces lag of standard EMA

Parameters

Parameter Type Description
data Array Price data array
period Number EMA calculation period

Example Usage

private dema21: number[] = bdpo.dema(data.close, 21);
private currentDEMA: number = this.dema21[this.dema21.length - 1];

// DEMA provides faster signals than regular EMA
// Good for trend following with reduced lag
bdpo.tema(data, period)
Triple Exponential Moving Average - minimal lag trend following

Parameters

Parameter Type Description
data Array Price data array
period Number EMA calculation period

Example Usage

private tema14: number[] = bdpo.tema(data.close, 14);
private currentTEMA: number = this.tema14[this.tema14.length - 1];

// TEMA offers the fastest response with minimal noise
// Excellent for scalping and short-term trading
bdpo.trima(data, period)
Triangular Moving Average - double smoothed for stability

Parameters

Parameter Type Description
data Array Price data array
period Number Triangular average period

Example Usage

private trima20: number[] = bdpo.trima(data.close, 20);
private currentTRIMA: number = this.trima20[this.trima20.length - 1];

// TRIMA is very smooth but has significant lag
// Best for identifying major trend direction
bdpo.hma(data, period)
Hull Moving Average - fast and smooth trend indicator

Parameters

Parameter Type Description
data Array Price data array
period Number HMA calculation period

Example Usage

private hma21: number[] = bdpo.hma(data.close, 21);
private currentHMA: number = this.hma21[this.hma21.length - 1];

// HMA combines speed and smoothness effectively
// Popular for swing trading strategies
bdpo.kama(data, period)
Kaufman Adaptive Moving Average - adjusts to market conditions

Parameters

Parameter Type Description
data Array Price data array
period Number Efficiency ratio period (typically 10)

Example Usage

private kama10: number[] = bdpo.kama(data.close, 10);
private currentKAMA: number = this.kama10[this.kama10.length - 1];

// KAMA adapts: fast in trends, slow in sideways markets
// Reduces whipsaws while maintaining responsiveness
bdpo.vwma(data, volume, period)
Volume Weighted Moving Average - weights prices by volume

Parameters

Parameter Type Description
data Array Price data array
volume Array Volume data array
period Number VWMA calculation period

Example Usage

private vwma20: number[] = bdpo.vwma(data.close, data.volume, 20);
private currentVWMA: number = this.vwma20[this.vwma20.length - 1];

// VWMA gives more weight to high-volume periods
// Better reflects institutional participation
bdpo.zlema(data, period)
Zero Lag Exponential Moving Average - eliminates lag through prediction

Parameters

Parameter Type Description
data Array Price data array
period Number EMA calculation period

Example Usage

private zlema20: number[] = bdpo.zlema(data.close, 20);
private currentZLEMA: number = this.zlema20[this.zlema20.length - 1];

// ZLEMA attempts to eliminate lag completely
// Can be more volatile but very responsive
bdpo.alma(data, period, offset, sigma)
Arnaud Legoux Moving Average - advanced adaptive smoothing

Parameters

Parameter Type Description
data Array Price data array
period Number Window length (typically 21)
offset Number Phase offset (0.0-1.0, typically 0.85)
sigma Number Smoothing factor (typically 6)

Example Usage

private alma: number[] = bdpo.alma(data.close, 21, 0.85, 6);
private currentALMA: number = this.alma[this.alma.length - 1];

// ALMA provides excellent balance between responsiveness and smoothness
// Highly configurable for different trading styles
bdpo.wilders(data, period)
Wilder's Smoothing - used in RSI and other Wilder indicators

Parameters

Parameter Type Description
data Array Price data array
period Number Smoothing period

Example Usage

private wilders14: number[] = bdpo.wilders(data.close, 14);
private currentWilders: number = this.wilders14[this.wilders14.length - 1];

// Wilder's smoothing is less aggressive than EMA
// Foundation for many classic technical indicators
bdpo.vidya(data, period)
Variable Index Dynamic Average - volatility adaptive moving average

Parameters

Parameter Type Description
data Array Price data array
period Number Base period for adaptation

Example Usage

private vidya14: number[] = bdpo.vidya(data.close, 14);
private currentVIDYA: number = this.vidya14[this.vidya14.length - 1];

// VIDYA adapts smoothing factor based on volatility
// Fast response in volatile markets, stable in quiet periods

Oscillators

Momentum indicators that oscillate between fixed bounds, ideal for identifying overbought/oversold conditions.

bdpo.rsi() Most Popular

Relative Strength Index (0-100) - Momentum oscillator

bdpo.macd() Popular

MACD - Trend following momentum indicator

bdpo.stoch()

Stochastic Oscillator - Price position within range

bdpo.cci()

Commodity Channel Index - Cyclical trend identifier

bdpo.willr()

Williams %R - Momentum indicator (-100 to 0)

bdpo.mfi()

Money Flow Index - Volume-weighted RSI

bdpo.rsi(data, period)
Relative Strength Index - Most popular momentum oscillator (0-100 scale)

Parameters

Parameter Type Description Common Values
data Array Price data array data.close
period Number Calculation period 14 (standard), 9, 21

Usage Example

// Standard RSI setup
private rsi14: number[] = bdpo.rsi(data.close, 14);
private currentRSI: number = this.rsi14[this.rsi14.length - 1];

// Trading signals
if (this.currentRSI > 70) {
    // Overbought - consider selling
} else if (this.currentRSI < 30) {
    // Oversold - consider buying
}
Typical Levels: Above 70 = Overbought, Below 30 = Oversold
bdpo.macd(data, fastPeriod, slowPeriod, signalPeriod)
Moving Average Convergence Divergence - Trend momentum indicator

Parameters

Parameter Type Description Standard Values
data Array Price data array data.close
fastPeriod Number Fast EMA period 12
slowPeriod Number Slow EMA period 26
signalPeriod Number Signal line period 9

Usage Example

// Standard MACD (12,26,9)
private macdData: any = bdpo.macd(data.close, 12, 26, 9);

// Access MACD components
private macdLine: number = this.macdData.macd[this.macdData.macd.length - 1];
private signalLine: number = this.macdData.signal[this.macdData.signal.length - 1];
private histogram: number = this.macdData.histogram[this.macdData.histogram.length - 1];

// Signal detection
if (this.macdLine > this.signalLine && this.histogram > 0) {
    // Bullish signal
}
Additional Oscillators
More momentum and oscillator indicators

bdpo.stochrsi()

Stochastic RSI - Stochastic applied to RSI values

bdpo.stochrsi(data.close, 14)

bdpo.ao()

Awesome Oscillator - Bill Williams momentum indicator

bdpo.ao(data.high, data.low)

bdpo.cmo()

Chande Momentum - Alternative momentum calculation

bdpo.cmo(data.close, 14)

bdpo.roc()

Rate of Change - Percentage price change

bdpo.roc(data.close, 12)

bdpo.ultosc()

Ultimate Oscillator - Multiple timeframe momentum

bdpo.ultosc(data.high, data.low, data.close, 7, 14, 28)

bdpo.tsi()

True Strength Index - Double smoothed momentum

bdpo.tsi(data.close, 25, 13)
bdpo.stoch(high, low, close, kPeriod, kSlowPeriod, dPeriod)
Stochastic Oscillator - momentum indicator comparing closing price to price range

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
kPeriod Number %K period (typically 14)
kSlowPeriod Number %K slowing period (typically 3)
dPeriod Number %D period (typically 3)

Example Usage

// Standard Stochastic (14,3,3)
private stochastic: any = bdpo.stoch(data.high, data.low, data.close, 14, 3, 3);
private kPercent: number = this.stochastic.k[this.stochastic.k.length - 1];
private dPercent: number = this.stochastic.d[this.stochastic.d.length - 1];

// Trading signals
if (this.kPercent > 80 && this.dPercent > 80) {
    // Overbought condition
} else if (this.kPercent < 20 && this.dPercent < 20) {
    // Oversold condition
}
bdpo.stochrsi(data, period)
Stochastic RSI - applies stochastic formula to RSI values

Parameters

Parameter Type Description
data Array Price data array
period Number RSI and Stochastic period (typically 14)

Example Usage

private stochRSI: number[] = bdpo.stochrsi(data.close, 14);
private currentStochRSI: number = this.stochRSI[this.stochRSI.length - 1];

// StochRSI signals (0-1 scale, more sensitive than regular RSI)
if (this.currentStochRSI > 0.8) {
    // Extremely overbought
} else if (this.currentStochRSI < 0.2) {
    // Extremely oversold
}
bdpo.willr(high, low, close, period)
Williams %R - momentum indicator ranging from -100 to 0

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
period Number Lookback period (typically 14)

Example Usage

private williamsR: number[] = bdpo.willr(data.high, data.low, data.close, 14);
private currentWillR: number = this.williamsR[this.williamsR.length - 1];

// Williams %R interpretation (inverted from typical overbought/oversold)
if (this.currentWillR > -20) {
    // Overbought (close to 0)
} else if (this.currentWillR < -80) {
    // Oversold (close to -100)
}
bdpo.cci(high, low, close, period)
Commodity Channel Index - identifies cyclical trends

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
period Number Calculation period (typically 20)

Example Usage

private cci20: number[] = bdpo.cci(data.high, data.low, data.close, 20);
private currentCCI: number = this.cci20[this.cci20.length - 1];

// CCI interpretation
if (this.currentCCI > 100) {
    // Strong uptrend, potential overbought
} else if (this.currentCCI < -100) {
    // Strong downtrend, potential oversold
}
bdpo.mfi(high, low, close, volume, period)
Money Flow Index - volume-weighted RSI

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
volume Array Volume array
period Number Calculation period (typically 14)

Example Usage

private mfi14: number[] = bdpo.mfi(data.high, data.low, data.close, data.volume, 14);
private currentMFI: number = this.mfi14[this.mfi14.length - 1];

// MFI interpretation (similar to RSI but includes volume)
if (this.currentMFI > 80) {
    // Overbought with volume confirmation
} else if (this.currentMFI < 20) {
    // Oversold with volume confirmation
}
bdpo.ao(high, low)
Awesome Oscillator - 34-period vs 5-period simple moving averages

Parameters

Parameter Type Description
high Array High price array
low Array Low price array

Example Usage

private awesomeOscillator: number[] = bdpo.ao(data.high, data.low);
private currentAO: number = this.awesomeOscillator[this.awesomeOscillator.length - 1];
private previousAO: number = this.awesomeOscillator[this.awesomeOscillator.length - 2];

// AO signals
if (this.currentAO > 0 && this.previousAO <= 0) {
    // Bullish momentum shift
} else if (this.currentAO < 0 && this.previousAO >= 0) {
    // Bearish momentum shift
}
bdpo.aroon(high, low, period)
Aroon - identifies trend changes and strength

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
period Number Lookback period (typically 14 or 25)

Returns

Object with aroonUp and aroonDown arrays (0-100 scale)

Example Usage

private aroonValues: any = bdpo.aroon(data.high, data.low, 25);
private aroonUp: number = this.aroonValues.aroonUp[this.aroonValues.aroonUp.length - 1];
private aroonDown: number = this.aroonValues.aroonDown[this.aroonValues.aroonDown.length - 1];

// Aroon trend analysis
if (this.aroonUp > 70 && this.aroonDown < 30) {
    // Strong uptrend
} else if (this.aroonDown > 70 && this.aroonUp < 30) {
    // Strong downtrend
} else if (Math.abs(this.aroonUp - this.aroonDown) < 20) {
    // Consolidation/sideways trend
}
bdpo.aroonosc(high, low, period)
Aroon Oscillator - difference between Aroon Up and Aroon Down

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
period Number Lookback period (typically 14 or 25)

Example Usage

private aroonOsc: number[] = bdpo.aroonosc(data.high, data.low, 25);
private currentAroonOsc: number = this.aroonOsc[this.aroonOsc.length - 1];

// Aroon Oscillator signals (-100 to +100)
if (this.currentAroonOsc > 40) {
    // Strong bullish trend
} else if (this.currentAroonOsc < -40) {
    // Strong bearish trend
} else {
    // Weak trend or consolidation
}
bdpo.bop(open, high, low, close)
Balance of Power - measures buying vs selling pressure

Parameters

Parameter Type Description
open Array Open price array
high Array High price array
low Array Low price array
close Array Close price array

Example Usage

private balanceOfPower: number[] = bdpo.bop(data.open, data.high, data.low, data.close);
private currentBOP: number = this.balanceOfPower[this.balanceOfPower.length - 1];

// BOP interpretation (-1 to +1 scale)
if (this.currentBOP > 0.5) {
    // Strong buying pressure
} else if (this.currentBOP < -0.5) {
    // Strong selling pressure
} else {
    // Balanced market
}
bdpo.cmo(data, period)
Chande Momentum Oscillator - momentum indicator with fixed bounds

Parameters

Parameter Type Description
data Array Price data array
period Number Calculation period (typically 14 or 20)

Example Usage

private cmo: number[] = bdpo.cmo(data.close, 14);
private currentCMO: number = this.cmo[this.cmo.length - 1];

// CMO signals (-100 to +100 scale)
if (this.currentCMO > 50) {
    // Overbought condition
} else if (this.currentCMO < -50) {
    // Oversold condition
}
bdpo.dpo(data, period)
Detrended Price Oscillator - removes trend from price data

Parameters

Parameter Type Description
data Array Price data array (typically close prices)
period Number Period for calculation (typically 20)

Example Usage

private readonly period: number = 20;
private closePrices: number[] = bdpo.getClosePrices();
private dpo: number[] = bdpo.dpo(this.closePrices, this.period);
private currentDPO: number = this.dpo[this.dpo.length - 1];

// DPO helps identify short-term cycles
if (this.currentDPO > 0) {
    // Price above detrended level - potential sell signal
} else if (this.currentDPO < 0) {
    // Price below detrended level - potential buy signal
}
bdpo.fisher(high, low, period)
Fisher Transform - converts price to Gaussian normal distribution

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
period Number Period for calculation (typically 10)

Example Usage

private readonly period: number = 10;
private highPrices: number[] = bdpo.getHighPrices();
private lowPrices: number[] = bdpo.getLowPrices();
private fisher: number[] = bdpo.fisher(this.highPrices, this.lowPrices, this.period);
private currentFisher: number = this.fisher[this.fisher.length - 1];

// Fisher Transform creates sharper turning points
if (this.currentFisher > 1.5) {
    // Overbought - potential reversal signal
} else if (this.currentFisher < -1.5) {
    // Oversold - potential reversal signal
}
bdpo.mom(data, period)
Momentum - rate of change between current and past values

Parameters

Parameter Type Description
data Array Price data array (typically close prices)
period Number Period for calculation (typically 10)

Example Usage

private readonly period: number = 10;
private closePrices: number[] = bdpo.getClosePrices();
private momentum: number[] = bdpo.mom(this.closePrices, this.period);
private currentMomentum: number = this.momentum[this.momentum.length - 1];

// Momentum indicates price velocity
if (this.currentMomentum > 0) {
    // Positive momentum - upward price movement
} else if (this.currentMomentum < 0) {
    // Negative momentum - downward price movement
}

// Look for momentum divergences with price
bdpo.ppo(data, fastPeriod, slowPeriod)
Percentage Price Oscillator - MACD expressed as percentage

Parameters

Parameter Type Description
data Array Price data array (typically close prices)
fastPeriod Number Fast EMA period (typically 12)
slowPeriod Number Slow EMA period (typically 26)

Example Usage

private readonly fastPeriod: number = 12;
private readonly slowPeriod: number = 26;
private closePrices: number[] = bdpo.getClosePrices();
private ppo: number[] = bdpo.ppo(this.closePrices, this.fastPeriod, this.slowPeriod);
private currentPPO: number = this.ppo[this.ppo.length - 1];

// PPO signals similar to MACD but normalized
if (this.currentPPO > 0) {
    // Bullish momentum - fast MA above slow MA
} else if (this.currentPPO < 0) {
    // Bearish momentum - fast MA below slow MA
}

// Look for PPO crossovers and divergences
bdpo.roc(data, period)
Rate of Change - percentage change over period

Parameters

Parameter Type Description
data Array Price data array
period Number Lookback period (typically 12 or 14)

Example Usage

private roc12: number[] = bdpo.roc(data.close, 12);
private currentROC: number = this.roc12[this.roc12.length - 1];

// ROC interpretation (percentage)
if (this.currentROC > 10) {
    // Strong bullish momentum (10% gain in 12 periods)
} else if (this.currentROC < -10) {
    // Strong bearish momentum (10% loss in 12 periods)
}
bdpo.ultosc(high, low, close, period1, period2, period3)
Ultimate Oscillator - multi-timeframe momentum oscillator

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
period1 Number Short period (typically 7)
period2 Number Medium period (typically 14)
period3 Number Long period (typically 28)

Example Usage

private ultimateOsc: number[] = bdpo.ultosc(data.high, data.low, data.close, 7, 14, 28);
private currentUO: number = this.ultimateOsc[this.ultimateOsc.length - 1];

// Ultimate Oscillator signals (0-100 scale)
if (this.currentUO > 70) {
    // Overbought - potential sell signal
} else if (this.currentUO < 30) {
    // Oversold - potential buy signal
}

Bands and Channels

bdpo.bbands(data, period, stddev)
Bollinger Bands - volatility bands around moving average

Parameters

Parameter Type Description
data Array Array of price values
period Number Moving average period
stddev Number Standard deviation multiplier

Example Usage

private bollingerBands: any = bdpo.bbands(data.close, 20, 2);
private upperBand: number = this.bollingerBands.upper[this.bollingerBands.upper.length - 1];
private lowerBand: number = this.bollingerBands.lower[this.bollingerBands.lower.length - 1];
bdpo.kc(high, low, close, period, multiplier)
Keltner Channels - volatility-based bands using ATR

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
period Number ATR calculation period
multiplier Number ATR multiplier for bands

Example Usage

private keltnerChannels: any = bdpo.kc(data.high, data.low, data.close, 20, 2.0);
private kcUpper: number = this.keltnerChannels.upper[this.keltnerChannels.upper.length - 1];
private kcLower: number = this.keltnerChannels.lower[this.keltnerChannels.lower.length - 1];
bdpo.dc(high, low, period)
Donchian Channels - price channel breakout system

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
period Number Lookback period

Example Usage

private donchianChannels: any = bdpo.dc(data.high, data.low, 20);
private dcUpper: number = this.donchianChannels.upper[this.donchianChannels.upper.length - 1];
private dcLower: number = this.donchianChannels.lower[this.donchianChannels.lower.length - 1];

// Breakout strategy
if (data.close[data.close.length - 1] > this.dcUpper) {
    // Price broke above upper channel - bullish breakout
}
bdpo.pbands(data, period)
Price Bands - percentage-based bands around moving average

Parameters

Parameter Type Description
data Array Price data array
period Number Moving average period

Example Usage

private priceBands: any = bdpo.pbands(data.close, 20);

Volume Indicators

bdpo.obv(close, volume)
On Balance Volume - cumulative volume indicator

Parameters

Parameter Type Description
close Array Array of close prices
volume Array Array of volume values

Example Usage

private onBalanceVolume: number[] = bdpo.obv(data.close, data.volume);
bdpo.vwap(high, low, close, volume)
Volume Weighted Average Price - average price weighted by volume

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
volume Array Volume array

Example Usage

private vwap: number[] = bdpo.vwap(data.high, data.low, data.close, data.volume);
private currentVWAP: number = this.vwap[this.vwap.length - 1];

// Compare current price to VWAP
if (data.close[data.close.length - 1] > this.currentVWAP) {
    // Price above VWAP - bullish sentiment
}
bdpo.ad(high, low, close, volume)
Accumulation/Distribution Line - volume-price momentum

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
volume Array Volume data array

Example Usage

private adLine: number[] = bdpo.ad(data.high, data.low, data.close, data.volume);
private currentAD: number = this.adLine[this.adLine.length - 1];
private previousAD: number = this.adLine[this.adLine.length - 2];

// Rising A/D line confirms uptrend
// Divergence between price and A/D suggests reversal
bdpo.cmf(high, low, close, volume, period)
Chaikin Money Flow - volume-weighted price momentum

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
volume Array Volume data array
period Number Calculation period (typically 20)

Example Usage

private cmf20: number[] = bdpo.cmf(data.high, data.low, data.close, data.volume, 20);
private currentCMF: number = this.cmf20[this.cmf20.length - 1];

// CMF above 0.1 = buying pressure
// CMF below -0.1 = selling pressure
bdpo.emv(high, low, volume, period)
Ease of Movement - price change per unit of volume

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
volume Array Volume data array
period Number Smoothing period (typically 14)

Example Usage

private emv14: number[] = bdpo.emv(data.high, data.low, data.volume, 14);
private currentEMV: number = this.emv14[this.emv14.length - 1];

// Positive EMV = price rising on low volume (easy movement)
// Negative EMV = price falling or high volume required
bdpo.fi(close, volume, period)
Force Index - combines price change with volume

Parameters

Parameter Type Description
close Array Close price array
volume Array Volume data array
period Number EMA smoothing period (typically 13)

Example Usage

private forceIndex: number[] = bdpo.fi(data.close, data.volume, 13);
private currentFI: number = this.forceIndex[this.forceIndex.length - 1];

// Rising FI confirms uptrend with volume
// Falling FI confirms downtrend with volume
bdpo.kvo(high, low, close, volume, fastPeriod, slowPeriod)
Klinger Volume Oscillator - volume-based momentum indicator

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
volume Array Volume data array
fastPeriod Number Fast EMA period (typically 34)
slowPeriod Number Slow EMA period (typically 55)

Example Usage

private kvo: number[] = bdpo.kvo(data.high, data.low, data.close, data.volume, 34, 55);
private currentKVO: number = this.kvo[this.kvo.length - 1];

// KVO crossover above zero = bullish volume momentum
// KVO crossover below zero = bearish volume momentum
bdpo.nvi(close, volume)
Negative Volume Index - tracks price performance on decreasing volume

Parameters

Parameter Type Description
close Array Close price array
volume Array Volume data array

Example Usage

private negativeVolumeIndex: number[] = bdpo.nvi(data.close, data.volume);
private currentNVI: number = this.negativeVolumeIndex[this.negativeVolumeIndex.length - 1];

// NVI tracks uninformed traders (retail) activity
// Rising NVI during low volume suggests retail accumulation
bdpo.pvi(close, volume)
Positive Volume Index - tracks price performance on increasing volume

Parameters

Parameter Type Description
close Array Close price array
volume Array Volume data array

Example Usage

private positiveVolumeIndex: number[] = bdpo.pvi(data.close, data.volume);
private currentPVI: number = this.positiveVolumeIndex[this.positiveVolumeIndex.length - 1];

// PVI tracks smart money (institutional) activity
// Rising PVI suggests institutions are accumulating

Volatility Indicators

bdpo.atr(high, low, close, period)
Average True Range - measure of volatility

Parameters

Parameter Type Description
high Array Array of high prices
low Array Array of low prices
close Array Array of close prices
period Number ATR calculation period

Example Usage

private atr14: number[] = bdpo.atr(data.high, data.low, data.close, 14);
bdpo.natr(high, low, close, period)
Normalized Average True Range - ATR as percentage of price

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
period Number Calculation period

Example Usage

// NATR gives volatility as percentage (0-100)
private natr14: number[] = bdpo.natr(data.high, data.low, data.close, 14);
private currentVolatility: number = this.natr14[this.natr14.length - 1];

// Use for position sizing based on volatility
if (this.currentVolatility > 5) {
    // High volatility - reduce position size
    const volatilityAdjustedSize = baseSize * (5 / this.currentVolatility);
}
bdpo.tr(high, low, close)
True Range - single period volatility measure

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array

Example Usage

private trueRange: number[] = bdpo.tr(data.high, data.low, data.close);
private currentTR: number = this.trueRange[this.trueRange.length - 1];
bdpo.cvi(high, low, period)
Chaikin's Volatility Index - volatility based on high-low range

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
period Number Calculation period

Example Usage

private chaikinVol: number[] = bdpo.cvi(data.high, data.low, 10);
bdpo.volatility(data, period)
Historical Volatility - annualized volatility of price changes

Parameters

Parameter Type Description
data Array Price data array
period Number Calculation period

Example Usage

private historicalVol: number[] = bdpo.volatility(data.close, 20);
private currentVol: number = this.historicalVol[this.historicalVol.length - 1];
bdpo.vhf(data, period)
Vertical Horizontal Filter - distinguishes trending vs trading markets

Parameters

Parameter Type Description
data Array Price data array
period Number Calculation period

Example Usage

private vhf28: number[] = bdpo.vhf(data.close, 28);
private currentVHF: number = this.vhf28[this.vhf28.length - 1];

// VHF > 0.35 typically indicates trending market
// VHF < 0.25 typically indicates trading/sideways market
if (this.currentVHF > 0.35) {
    // Use trend-following strategy
} else if (this.currentVHF < 0.25) {
    // Use mean-reversion strategy
}

Trend Indicators

bdpo.apo(close, shortPeriod, longPeriod)
Absolute Price Oscillator - difference between fast and slow MA

Parameters

Parameter Type Description
close Array Close price array
shortPeriod Number Short period for fast MA
longPeriod Number Long period for slow MA

Example Usage

private apo: number[] = bdpo.apo(data.close, 12, 26);
bdpo.dx(high, low, close, period)
Directional Index - measures trend strength

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
period Number Calculation period

Example Usage

private dx14: number[] = bdpo.dx(data.high, data.low, data.close, 14);
bdpo.di(high, low, close, period)
Directional Indicator - returns Plus DI and Minus DI

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
period Number Calculation period (typically 14)

Returns

Object with plus and minus arrays representing +DI and -DI values

Example Usage

private diValues: any = bdpo.di(data.high, data.low, data.close, 14);
private plusDI: number = this.diValues.plus[this.diValues.plus.length - 1];
private minusDI: number = this.diValues.minus[this.diValues.minus.length - 1];

// DI crossover signals
if (this.plusDI > this.minusDI) {
    // Bullish trend - +DI above -DI
} else if (this.minusDI > this.plusDI) {
    // Bearish trend - -DI above +DI
}
bdpo.dm(high, low, period)
Directional Movement - measures directional price movement

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
period Number Smoothing period (typically 14)

Returns

Object with plus and minus arrays representing +DM and -DM values

Example Usage

private dmValues: any = bdpo.dm(data.high, data.low, 14);
private plusDM: number = this.dmValues.plus[this.dmValues.plus.length - 1];
private minusDM: number = this.dmValues.minus[this.dmValues.minus.length - 1];

// DM analysis for trend direction
private dmRatio: number = this.plusDM / this.minusDM;
if (this.dmRatio > 1.5) {
    // Strong upward movement
} else if (this.dmRatio < 0.67) {
    // Strong downward movement
}
bdpo.adx(high, low, close, period)
Average Directional Index - measures trend strength

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
period Number Calculation period (typically 14)

Example Usage

private adx: number[] = bdpo.adx(data.high, data.low, data.close, 14);
private currentADX: number = this.adx[this.adx.length - 1];

// ADX trend strength interpretation
if (this.currentADX > 40) {
    // Very strong trend
} else if (this.currentADX > 25) {
    // Strong trend
} else if (this.currentADX > 20) {
    // Moderate trend
} else {
    // Weak or no trend
}
bdpo.adxr(high, low, close, period)
Average Directional Index Rating - smoothed ADX

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
close Array Close price array
period Number Calculation period (typically 14)

Example Usage

private adxr: number[] = bdpo.adxr(data.high, data.low, data.close, 14);
private currentADXR: number = this.adxr[this.adxr.length - 1];

// ADXR provides smoother trend strength readings
if (this.currentADXR > 30) {
    // Strong sustained trend
} else if (this.currentADXR < 20) {
    // Weak trend or sideways market
}
bdpo.psar(high, low, step, max)
Parabolic SAR - stop and reversal points for trend following

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
step Number Acceleration factor step
max Number Maximum acceleration factor

Example Usage

private psar: number[] = bdpo.psar(data.high, data.low, 0.02, 0.2);
private currentPSAR: number = this.psar[this.psar.length - 1];
private currentClose: number = data.close[data.close.length - 1];

// PSAR trading signals
if (this.currentClose > this.currentPSAR) {
    // Bullish signal - price above PSAR
} else if (this.currentClose < this.currentPSAR) {
    // Bearish signal - price below PSAR
}
bdpo.trix(close, period)
TRIX - triple exponentially smoothed moving average oscillator

Parameters

Parameter Type Description
close Array Close price array
period Number Smoothing period (typically 14)

Example Usage

private trix: number[] = bdpo.trix(data.close, 14);
private currentTRIX: number = this.trix[this.trix.length - 1];
private previousTRIX: number = this.trix[this.trix.length - 2];

// TRIX signals
if (this.currentTRIX > 0 && this.previousTRIX <= 0) {
    // Bullish signal - TRIX crosses above zero
} else if (this.currentTRIX < 0 && this.previousTRIX >= 0) {
    // Bearish signal - TRIX crosses below zero
}
bdpo.linreg(close, period)
Linear Regression - trend line based on least squares method

Parameters

Parameter Type Description
close Array Close price array
period Number Regression period (typically 14-21)

Example Usage

private linreg: number[] = bdpo.linreg(data.close, 20);
private currentLR: number = this.linreg[this.linreg.length - 1];
private currentPrice: number = data.close[data.close.length - 1];

// Linear regression trend analysis
private deviation: number = this.currentPrice - this.currentLR;
private deviationPercent: number = (deviation / this.currentLR) * 100;

if (Math.abs(this.deviationPercent) > 2) {
    // Price significantly deviates from trend
}
bdpo.midpoint(values, period)
Midpoint - average of highest and lowest values over period

Parameters

Parameter Type Description
values Array Input data array
period Number Lookback period

Example Usage

private midpoint: number[] = bdpo.midpoint(data.close, 14);
private currentMP: number = this.midpoint[this.midpoint.length - 1];
private currentPrice: number = data.close[data.close.length - 1];

// Midpoint as support/resistance level
if (this.currentPrice > this.currentMP) {
    // Price above midpoint - potential bullish
} else if (this.currentPrice < this.currentMP) {
    // Price below midpoint - potential bearish
}
bdpo.midprice(high, low, period)
Midprice - average of highest high and lowest low over period

Parameters

Parameter Type Description
high Array High price array
low Array Low price array
period Number Lookback period

Example Usage

private midprice: number[] = bdpo.midprice(data.high, data.low, 14);
private currentMidprice: number = this.midprice[this.midprice.length - 1];
private currentClose: number = data.close[data.close.length - 1];

// Midprice as dynamic support/resistance
private distanceFromMidprice: number = this.currentClose - this.currentMidprice;
private distancePercent: number = (distanceFromMidprice / this.currentMidprice) * 100;

if (Math.abs(this.distancePercent) > 1.5) {
    // Price significantly away from recent midprice
}

Cross-over Indicators

bdpo.crossover(series1, series2)
Crossover - when one series crosses above another

Parameters

Parameter Type Description
series1 Array First data series
series2 Array Second data series

Example Usage

private fastMA: number[] = bdpo.ema(data.close, 12);
private slowMA: number[] = bdpo.ema(data.close, 26);
private crossUp: boolean[] = bdpo.crossover(this.fastMA, this.slowMA);
bdpo.crossunder(series1, series2)
Crossunder - when one series crosses below another

Parameters

Parameter Type Description
series1 Array First data series
series2 Array Second data series

Example Usage

private fastMA: number[] = bdpo.ema(data.close, 12);
private slowMA: number[] = bdpo.ema(data.close, 26);
private crossDown: boolean[] = bdpo.crossunder(this.fastMA, this.slowMA);

// Check for bearish crossover signal
if (this.crossDown[this.crossDown.length - 1]) {
    // Fast MA crossed below slow MA - bearish signal
}
bdpo.crossany(series1, series2)
Crossany - when one series crosses another in any direction

Parameters

Parameter Type Description
series1 Array First data series
series2 Array Second data series

Example Usage

private rsi: number[] = bdpo.rsi(data.close, 14);
private rsiCrosses70: boolean[] = bdpo.crossany(this.rsi, [70]); // Cross 70 level

// Detect any crossover of RSI with 70 level
if (this.rsiCrosses70[this.rsiCrosses70.length - 1]) {
    // RSI crossed 70 level in either direction
}
bdpo.crossOverNumber(series, number)
CrossOverNumber - when series crosses above a specific number

Parameters

Parameter Type Description
series Array Data series to check
number Number Level to check for crossover

Example Usage

private rsi: number[] = bdpo.rsi(data.close, 14);
private rsiCrossesAbove30: boolean[] = bdpo.crossOverNumber(this.rsi, 30);

// Check if RSI crosses above 30 (exit oversold)
if (this.rsiCrossesAbove30[this.rsiCrossesAbove30.length - 1]) {
    // RSI just crossed above 30 - potential buy signal
}

Complete Strategy Example

Multi-Indicator Strategy
Example combining multiple indicators in a trading strategy
class MultiIndicatorStrategy {
    // Public configuration
    public rsiPeriod: number = 14;
    public smaPeriod: number = 20;
    public bbandsPeriod: number = 20;
    public bbandsStdDev: number = 2;
    
    // Private variables
    private rsi: number[] = [];
    private sma: number[] = [];
    private bbands: any = null;
    private atr: number[] = [];
    
    public __init__(): void {
        console.log("Multi-indicator strategy initialized");
    }
    
    public __tick__(data: any): void {
        // Calculate all indicators
        this.rsi = bdpo.rsi(data.close, this.rsiPeriod);
        this.sma = bdpo.sma(data.close, this.smaPeriod);
        this.bbands = bdpo.bbands(data.close, this.bbandsPeriod, this.bbandsStdDev);
        this.atr = bdpo.atr(data.high, data.low, data.close, 14);
        
        const currentPrice: number = data.close[data.close.length - 1];
        const currentRSI: number = this.rsi[this.rsi.length - 1];
        const currentSMA: number = this.sma[this.sma.length - 1];
        const upperBand: number = this.bbands.upper[this.bbands.upper.length - 1];
        const lowerBand: number = this.bbands.lower[this.bbands.lower.length - 1];
        const currentATR: number = this.atr[this.atr.length - 1];
        
        // Entry conditions
        const bullishCondition: boolean = currentPrice > currentSMA && 
                                        currentRSI < 70 && 
                                        currentPrice > lowerBand;
                                        
        const bearishCondition: boolean = currentPrice < currentSMA && 
                                        currentRSI > 30 && 
                                        currentPrice < upperBand;
        
        // Execute trades
        if (bullishCondition) {
            const stopLoss: number = currentPrice - (currentATR * 2);
            const takeProfit: number = currentPrice + (currentATR * 3);
            bdpo.orderSend("BUY", 0.1, stopLoss, takeProfit);
        } else if (bearishCondition) {
            const stopLoss: number = currentPrice + (currentATR * 2);
            const takeProfit: number = currentPrice - (currentATR * 3);
            bdpo.orderSend("SELL", 0.1, stopLoss, takeProfit);
        }
    }
    
    public __shutdown__(): void {
        console.log("Multi-indicator strategy completed");
    }
}
Performance Note: Indicators with large periods may have initial null values. Always check for valid data before using in calculations. Use proper error handling in production strategies.

Trade Functions

Order execution and trade management functionality

Note: All trade functions are accessible via the bdpo object and handle both live trading and backtesting scenarios automatically.

Order Execution

bdpo.orderSend(options)
Main order execution function supporting all order types

Parameters

Parameter Type Description Required
options.orderType String Order type: BUY, SELL, BUYLIMIT, SELLLIMIT, BUYSTOP, SELLSTOP
options.lotSize Number Position size/lot size
options.price Number Order price (required for limit/stop orders)
options.stopLoss Number Stop loss price level
options.takeProfit Number Take profit price level
options.note String Order notes/comments

Returns

Promise - Resolves with order object or rejects with error

Example Usage

// Market buy order
await bdpo.orderSend({
    orderType: "BUY",
    lotSize: 1000,
    stopLoss: 95.0,
    takeProfit: 105.0,
    note: "Bullish breakout entry"
});

// Limit order with risk management
await bdpo.orderSend({
    orderType: "BUYLIMIT",
    lotSize: 500,
    price: 98.50,
    stopLoss: 96.00,
    takeProfit: 103.00
});

// Stop order for trend following
await bdpo.orderSend({
    orderType: "BUYSTOP",
    lotSize: 750,
    price: 102.00,
    stopLoss: 99.50
});

Position Management

bdpo.orderCloseAll()
Closes all open and pending positions

Parameters

No parameters required

Returns

Void - Executes close orders for all positions

Example Usage

// Emergency close all positions
bdpo.orderCloseAll();

// Close all positions at end of trading session
if (bdpo.isMarketClose()) {
    bdpo.orderCloseAll();
}

Order Validation & Best Practices

Order Validation Examples
Best practices for order validation and error handling

Example Usage

// Comprehensive order validation and execution
class OrderManager {
    public maxPositionSize: number = 1000;
    public maxRiskPercent: number = 2;
    
    public async sendOrder(orderType: string, lotSize: number, options: any = {}): Promise {
        try {
            // Validate position size
            if (lotSize > this.maxPositionSize) {
                bdpo.log(`Order size ${lotSize} exceeds maximum ${this.maxPositionSize}`);
                return false;
            }
            
            // Check account balance for risk management
            const balance = bdpo.getBalance();
            const riskAmount = balance * (this.maxRiskPercent / 100);
            const currentPrice = bdpo.getPrice();
            const orderValue = lotSize * currentPrice;
            
            if (orderValue > riskAmount) {
                bdpo.log(`Order value exceeds risk limit: ${orderValue} > ${riskAmount}`);
                return false;
            }
            
            // Execute order with proper error handling
            await bdpo.orderSend({
                orderType: orderType,
                lotSize: lotSize,
                stopLoss: options.stopLoss,
                takeProfit: options.takeProfit,
                note: options.note || `Auto order: ${orderType}`
            });
            
            bdpo.log(`Order executed successfully: ${orderType} ${lotSize}`);
            return true;
            
        } catch (error) {
            bdpo.log(`Order execution failed: ${error.message}`);
            return false;
        }
    }
}
Trade Risk Management
Position sizing and risk control strategies

Example Usage

// Advanced position sizing with ATR-based stops
class RiskManagedTrading {
    public riskPerTrade: number = 0.02; // 2% risk per trade
    
    public calculatePositionSize(entryPrice: number, stopLoss: number): number {
        const balance = bdpo.getBalance();
        const riskAmount = balance * this.riskPerTrade;
        const riskPerShare = Math.abs(entryPrice - stopLoss);
        
        if (riskPerShare === 0) {
            bdpo.log("Error: Stop loss equals entry price");
            return 0;
        }
        
        const positionSize = Math.floor(riskAmount / riskPerShare);
        bdpo.log(`Position size calculated: ${positionSize} shares`);
        return positionSize;
    }
    
    public async placeManagedOrder(orderType: string, entryPrice: number, atrValue: number): Promise {
        // Calculate dynamic stop loss based on ATR
        const stopMultiplier = 2.0;
        const stopLoss = orderType === "BUY" 
            ? entryPrice - (atrValue * stopMultiplier)
            : entryPrice + (atrValue * stopMultiplier);
            
        const takeProfit = orderType === "BUY"
            ? entryPrice + (atrValue * 3.0) // 3:1 risk-reward
            : entryPrice - (atrValue * 3.0);
            
        const positionSize = this.calculatePositionSize(entryPrice, stopLoss);
        
        if (positionSize > 0) {
            await bdpo.orderSend({
                orderType: orderType,
                lotSize: positionSize,
                price: orderType.includes("LIMIT") || orderType.includes("STOP") ? entryPrice : undefined,
                stopLoss: stopLoss,
                takeProfit: takeProfit,
                note: `ATR-based order: R=${this.riskPerTrade * 100}%`
            });
        }
    }
}

Order Types

Market Orders

BUY - Immediate buy at current ask price
SELL - Immediate sell at current bid price

Limit Orders

BUYLIMIT - Buy when price drops to specified level
SELLLIMIT - Sell when price rises to specified level

Stop Orders

BUYSTOP - Buy when price breaks above specified level
SELLSTOP - Sell when price breaks below specified level

Risk Management: Always use stop loss and take profit levels to manage risk. The system validates that stop loss and take profit levels are logically placed relative to the order price.

Position Functions

Position tracking and portfolio management

Note: Position functions provide access to current portfolio state, including open, pending, and closed positions.

Position Retrieval

bdpo.getOpenPositions()
Get all currently open positions

Returns

Array - Array of open position objects

Example Usage

// Get all open positions
const openPositions: any[] = bdpo.getOpenPositions();

// Check if we have any long positions
const longPositions = openPositions.filter(pos => pos.side === 'BUY');
bdpo.log(`Currently holding ${longPositions.length} long positions`);

// Calculate total position value
const totalValue = openPositions.reduce((sum, pos) => sum + (pos.size * pos.price), 0);
bdpo.getPendingPositions()
Get all pending orders that haven't been executed yet

Returns

Array - Array of pending order objects

Example Usage

// Get pending orders
const pendingOrders: any[] = bdpo.getPendingPositions();

// Check for pending buy orders
const pendingBuys = pendingOrders.filter(order => 
    order.side === 'BUYLIMIT' || order.side === 'BUYSTOP'
);

// Cancel old pending orders (if needed)
if (pendingBuys.length > 5) {
    bdpo.log("Too many pending buy orders, consider cleanup");
}
bdpo.getClosedPositions()
Get all closed/completed positions

Returns

Array - Array of closed position objects

Example Usage

// Analyze trading performance
const closedPositions: any[] = bdpo.getClosedPositions();

// Calculate win rate
const winners = closedPositions.filter(pos => pos.profit > 0);
const winRate = (winners.length / closedPositions.length) * 100;

// Calculate total P&L
const totalPnL = closedPositions.reduce((sum, pos) => sum + pos.profit, 0);

bdpo.log(`Win Rate: ${winRate.toFixed(2)}%, Total P&L: $${totalPnL.toFixed(2)}`);
bdpo.getTotalOpenPositions()
Get the count of currently open positions

Returns

Number - Number of open positions

Example Usage

// Quick position count check
const openCount: number = bdpo.getTotalOpenPositions();

// Position management based on count
if (openCount >= 10) {
    bdpo.log("High position count - monitoring closely");
} else if (openCount === 0) {
    bdpo.log("No open positions - ready for new trades");
}

bdpo.log(`Current open positions: ${openCount}`);
bdpo.getTotalPendingPositions()
Get the count of pending orders

Returns

Number - Number of pending orders

Example Usage

// Monitor pending order count
const pendingCount: number = bdpo.getTotalPendingPositions();

// Cleanup strategy for too many pending orders
if (pendingCount > 20) {
    bdpo.log("Too many pending orders - consider cleanup");
    // Implementation to cancel old orders would go here
}

bdpo.log(`Pending orders: ${pendingCount}`);
bdpo.getTotalClosedPositions()
Get the count of closed positions

Returns

Number - Number of closed positions

Example Usage

// Track trading activity
const closedCount: number = bdpo.getTotalClosedPositions();

// Performance analysis trigger
if (closedCount >= 100) {
    bdpo.log("Milestone reached - analyzing 100 completed trades");
    // Trigger performance analysis
}

bdpo.log(`Total completed trades: ${closedCount}`);

Position Limits

bdpo.getMaxOpenPositions()
Get the maximum allowed open positions

Returns

Number - Maximum number of concurrent open positions (default: 50)

Example Usage

// Check position limits before opening new trades
const maxPositions: number = bdpo.getMaxOpenPositions();
const currentPositions: number = bdpo.getOpenPositions().length;

if (currentPositions < maxPositions) {
    // Safe to open new position
    await bdpo.orderSend({
        orderType: "BUY",
        lotSize: 1000
    });
} else {
    bdpo.log("Maximum positions reached, waiting for exits");
}

Position Object Structure

Position Properties
Standard properties available on position objects
Property Type Description
id String Unique position identifier
symbol String Trading symbol (e.g., "AAPL", "EURUSD")
side String Position side: BUY, SELL, BUYLIMIT, etc.
size Number Position size/lot size
price Number Entry price
stopLoss Number Stop loss price level
takeProfit Number Take profit price level
openDate String ISO timestamp when position was opened
profit Number Current unrealized P&L (closed positions: realized P&L)
Best Practice: Regularly monitor position counts and implement position limits in your strategies to prevent overexposure and manage risk effectively.

Account Functions

Account balance and equity management

Note: Account functions provide real-time access to account balance, equity, and profit/loss metrics.

Balance Management

bdpo.getBalance()
Get current account balance

Returns

Number - Current account balance

Example Usage

// Get current balance
const balance: number = bdpo.getBalance();

// Calculate position size based on risk percentage
const riskPercent: number = 2; // 2% risk per trade
const riskAmount: number = balance * (riskPercent / 100);

// Use balance for position sizing
const currentPrice: number = bdpo.getPrice();
const maxPositionSize: number = riskAmount / currentPrice;

bdpo.log(`Account Balance: $${balance.toFixed(2)}`);
bdpo.getEquity()
Get current account equity (balance + unrealized P&L)

Returns

Number - Current account equity

Example Usage

// Monitor account equity
const equity: number = bdpo.getEquity();
const balance: number = bdpo.getBalance();
const unrealizedPnL: number = equity - balance;

// Check margin usage
const marginUsage: number = ((balance - equity) / balance) * 100;

if (marginUsage > 50) {
    bdpo.log("Warning: High margin usage detected");
    // Consider reducing position sizes
}

bdpo.log(`Equity: $${equity.toFixed(2)}, Unrealized P&L: $${unrealizedPnL.toFixed(2)}`);
bdpo.getProfit()
Get current total profit/loss

Returns

Number - Current profit/loss amount

Example Usage

// Track performance metrics
const profit: number = bdpo.getProfit();
const balance: number = bdpo.getBalance();
const profitPercent: number = (profit / balance) * 100;

// Implement profit protection
if (profitPercent > 10) {
    bdpo.log("Profit target reached, reducing risk");
    // Reduce position sizes or close some positions
}

// Stop loss at account level
if (profitPercent < -5) {
    bdpo.log("Daily loss limit reached, closing all positions");
    bdpo.orderCloseAll();
}

bdpo.log(`Current P&L: $${profit.toFixed(2)} (${profitPercent.toFixed(2)}%)`);

Risk Management Examples

Account-Based Risk Management
Practical examples of account-based risk controls
class RiskManagedStrategy {
    public maxRiskPercent: number = 2;      // Max 2% risk per trade
    public maxDailyLoss: number = -5;       // Stop trading at -5% daily loss
    public profitTarget: number = 10;       // Take profits at +10%
    
    private _initialBalance: number = 0;

    __init__(): void {
        this._initialBalance = bdpo.getBalance();
    }

    __tick__(): any {
        // Calculate current performance
        const currentBalance: number = bdpo.getBalance();
        const dailyPnL: number = ((currentBalance - this._initialBalance) / this._initialBalance) * 100;
        
        // Check daily loss limit
        if (dailyPnL <= this.maxDailyLoss) {
            bdpo.log("Daily loss limit reached - stopping trading");
            bdpo.orderCloseAll();
            return { status: "stopped", reason: "daily_loss_limit" };
        }
        
        // Check profit target
        if (dailyPnL >= this.profitTarget) {
            bdpo.log("Daily profit target reached - reducing risk");
            // Reduce position sizes or close half positions
        }
        
        // Calculate position size based on account risk
        const riskAmount: number = currentBalance * (this.maxRiskPercent / 100);
        const currentPrice: number = bdpo.getPrice();
        const stopLossDistance: number = currentPrice * 0.02; // 2% stop loss
        const positionSize: number = riskAmount / stopLossDistance;
        
        return {
            accountMetrics: {
                balance: currentBalance,
                dailyPnL: dailyPnL,
                riskAmount: riskAmount,
                positionSize: positionSize
            }
        };
    }
}
Important: Always implement account-level risk controls. Monitor equity, set daily loss limits, and use position sizing based on account balance to protect your capital.

Plotting Functions

Chart visualization and overlay capabilities for custom indicators and signals

Quick Reference: Plotting functions enable you to create custom chart overlays, visualize indicators, plot signals, and display trading insights directly on the chart. The `plot()` function supports multiple plot types including lines, histograms, arrows, and custom shapes with full styling control.

Basic Plotting

bdpo.plot(stringType, dataArray, propsObject?, settingsObject?)
Create chart overlays and visualizations with customizable styling

Parameters

Parameter Type Description
stringType String Plot type: "splines", "histogram", "area", "band", "range", "sparse", "candles", "arrowTrades", "cloud", "priceLabels", "superBands", "trades"
dataArray Array | Object Data format depends on plot type (see data formats below)
propsObject Object (optional) Styling properties: color, width, opacity, etc.
settingsObject Object (optional) Available settings: precision

Data Formats by Plot Type

Plot Type Reference:
  • splines: [line1, line2, ...] - Multiple line plots
  • range: [value] - Single value range plot
  • sparse: [value, ?direction] - Sparse data with optional direction
  • area: [value] - Area fill plot
  • band: [high, mid, low] - Three-line band plot
  • candles: [open, high, low, close, ?volume] - Candlestick chart
  • arrowTrades: {high, low, close, trades: [[dir, ?label, ?big], ...]} - Trade arrows
  • cloud: [line1, line2] - Cloud between two lines
  • histogram: [hist, ?value, ?signal] - Histogram with optional value and signal
  • priceLabels: {text, dir, pin, color?, back?, stroke?, offset?} - Price labels
  • superBands: [high1, mid1, low1, high2, mid2, low2] - Multiple bands
  • trades: [dir, price, ?label] - Trade markers

Properties by Plot Type

Range Properties
For "range" plot type - displays range bands with upper/lower bounds
Property Type Default Description
showRange boolean true Show/hide the range plot
rangeColor Color #ec206e Color of the range line
rangeBackColor Color #381e9c16 Background color of the range area
rangeBandColor Color #535559 Color of the range band lines
rangeLineWidth number 1 Width of the range lines
upperBand number 70 Upper range band value
lowerBand number 30 Lower range band value
Spline Properties
For "splines" plot type - displays line charts and trend lines
Property Type Default Description
showSpline boolean true Show/hide the spline plot
splineColor Color #31ce31 Color of the spline line
splineWidth number 2 Width of the spline line
skipNan boolean true Skip NaN values when drawing the line
Sparse Properties
For "sparse" plot type - displays scattered data points
Property Type Default Description
showSparse boolean true Show/hide the sparse plot
sparseColor Color #5700ed Color of the sparse points
sparseSize number 3 Size of the sparse points
sparseShape string point Shape of the sparse points
Area Properties
For "area" plot type - displays filled area charts
Property Type Default Description
showArea boolean true Show/hide the area plot
areaColor Color #31ce31 Color of the area line
areaLineWidth number 1.25 Width of the area line
areaBack1 Color areaColor + '65' Primary background color (gradient start)
areaBack2 Color areaColor + '01' Secondary background color (gradient end)
Band Properties
For "band" plot type - displays three-line bands (upper, middle, lower)
Property Type Default Description
showBand boolean true Show/hide the band plot
upperBandColor Color #00dbeb Color of the upper band line
lowerBandColor Color #00dbeb Color of the lower band line
middleBandColor Color #00dbeb Color of the middle band line
bandBackColor Color #00dbeb22 Background color of the band area
bandLineWidth number 1 Width of the band lines
showMid boolean true Show/hide the middle band line
ArrowTrades Properties
For "arrowTrades" plot type - displays buy/sell trade arrows
Property Type Default Description
showArrowTrades boolean true Show/hide the arrow trades
arrowBuyColor Color #08c65e Color of buy arrows
arrowSellColor Color #e42633 Color of sell arrows
arrowSize number 7 Size of the arrow markers
arrowShowLabels boolean true Show/hide arrow labels
arrowMarkerOutline boolean true Show outline around arrow markers
arrowOutlineWidth number 4 Width of the arrow outline
Candles Properties
For "candles" plot type - displays OHLCV candlestick charts
Property Type Default Description
showCandles boolean true Show/hide the candles
colorBodyUp color core.colors.candleUp Color of bullish candle bodies
colorBodyDw color core.colors.candleDw Color of bearish candle bodies
colorWickUp color core.colors.wickUp Color of bullish candle wicks
colorWickDw color core.colors.wickDw Color of bearish candle wicks
colorVolUp color core.colors.volUp Color of bullish volume bars
colorVolDw color core.colors.volDw Color of bearish volume bars
showVolume boolean true Show/hide volume bars
currencySymbol string '' Currency symbol for price labels
showAvgVolume boolean true Show/hide average volume line
avgVolumeSMA number 20 Period for average volume calculation
colorAvgVol color #1cccb777 Color of the average volume line
Cloud Properties
For "cloud" plot type - displays cloud areas between two lines
Property Type Default Description
showCloud boolean true Show/hide the cloud
cloudColor1 color #55d7b0aa Color of the first cloud line
cloudColor2 color #d94d64aa Color of the second cloud line
cloudBack1 color #79ffde60 Background color when line1 > line2
cloudBack2 color #ff246c60 Background color when line2 > line1
cloudDrawLines boolean true Draw the cloud boundary lines
Histogram Properties
For "histogram" plot type - displays bar charts and oscillators
Property Type Default Description
showHistogram boolean true Show/hide the histogram
histBarWidth number 4 Width of histogram bars
histLineWidth number 1 Width of histogram bar outlines
histColorUp Color #35a776 Color for positive histogram values
histColorDw Color #e54150 Color for negative histogram values
histColorSemiUp Color #79e0b3 Color for weak positive values
histColorSemiDw Color #ea969e Color for weak negative values
histColorValue Color #3782f2 Color for value line
histColorSignal Color #f48709 Color for signal line
PriceLabels Properties
For "priceLabels" plot type - displays price labels on the chart (only visible on main chart or overlays with candles)
Property Type Default Description
showPriceLabels boolean true Show/hide price labels
labelColor Color core.colors.text Text color of the labels
labelBack Color core.colors.back + '00' Background color of the labels
labelStroke Color core.colors.scale + '00' Stroke color of the labels
labelBorderRadius number 3 Border radius of label boxes
labelOffset number 5 Offset distance from pin point
SuperBands Properties
For "superBands" plot type - displays multiple overlapping band systems
Property Type Default Description
showSuperBands boolean true Show/hide the super bands
superBandsColor1 color #d80d3888 Color of the first band system
superBandsColor1dark color #d80d3888 Dark theme color of the first band system
superBandsColor2 color #1edbbe88 Color of the second band system
superBandsColor2dark color #1edbbe88 Dark theme color of the second band system
Trades Properties
For "trades" plot type - displays trade execution markers
Property Type Default Description
showTrades boolean true Show/hide trade markers
tradeBuyColor color #08b2c6 Color of buy trade markers
tradeSellColor color #e42633 Color of sell trade markers
tradeRadius number 4 Radius of trade marker circles
tradeShowLabels boolean true Show/hide trade labels
tradeMarkerOutline boolean true Show outline around trade markers
tradeOutlineWidth number 4 Width of the trade marker outline

Returns

void - Adds plot to chart overlay queue

Example Usage

// Basic spline plot with custom properties
const prices: number[] = bdpo.getClose(data);
const customMA: number[] = bdpo.sma(prices, 20);

bdpo.plot("splines", [customMA], {
  splineColor: "#2196F3",
  splineWidth: 2,
  skipNan: true
}, {
  precision: 4
});

// Range plot for oscillator with custom bands
const rsi: number[] = bdpo.rsi(prices, 14);

bdpo.plot("range", [rsi], {
  rangeColor: "#9C27B0",
  rangeBackColor: "#9C27B022",
  rangeBandColor: "#FF5722",
  rangeLineWidth: 2,
  upperBand: 70,
  lowerBand: 30
}, {
  precision: 5
});

// Area plot with gradient fill
const atr: number[] = bdpo.atr(bdpo.getHigh(data), bdpo.getLow(data), prices, 14);
const upperBand: number[] = prices.map((price, i) => price + atr[i] * 2);

bdpo.plot("area", [upperBand], {
  areaColor: "#4CAF50",
  areaLineWidth: 1.5,
  areaBack1: "#4CAF5080",
  areaBack2: "#4CAF5010"
}, {
  precision: 5
});

// Band plot for Bollinger Bands
const bbands = bdpo.bbands(prices, 20, 2);

bdpo.plot("band", [bbands.upper, bbands.middle, bbands.lower], {
  upperBandColor: "#2196F3",
  middleBandColor: "#FF9800",
  lowerBandColor: "#2196F3",
  bandBackColor: "#2196F320",
  bandLineWidth: 1,
  showMid: true
}, {
  precision: 5
});

// Histogram with conditional colors
const macd = bdpo.macd(prices, 12, 26, 9);

bdpo.plot("histogram", [macd.histogram], {
  histColorUp: "#4CAF50",
  histColorDw: "#F44336",
  histColorSemiUp: "#8BC34A",
  histColorSemiDw: "#FFAB91",
  histBarWidth: 6,
  histLineWidth: 1
}, {
  precision: 5
});

// Trade arrows with custom styling
bdpo.plot("arrowTrades", {
  high: bdpo.getHigh(data),
  low: bdpo.getLow(data),
  close: prices,
  trades: [[1, "BUY", false], [-1, "SELL", true]]
}, {
  arrowBuyColor: "#00E676",
  arrowSellColor: "#FF1744",
  arrowSize: 8,
  arrowShowLabels: true,
  arrowMarkerOutline: true,
  arrowOutlineWidth: 3
}, {
  precision: 5
});

// Price labels with custom styling
bdpo.plot("priceLabels", {
  text: "Important Level",
  dir: 1,
  pin: "close",
  color: "#FFFFFF",
  back: "#FF5722",
  stroke: "#D84315",
  offset: 10
}, {
  labelColor: "#FFFFFF",
  labelBack: "#FF572280",
  labelStroke: "#D84315",
  labelBorderRadius: 5,
  labelOffset: 8
}, {
  precision: 2
});

Spline Plots

Spline Plot Examples
Create trend lines, moving averages, and continuous indicators using the "splines" plot type
Properties Reference:

For detailed spline properties, see the Spline Properties section above. Key properties include splineColor, splineWidth, and skipNan.

Example Usage

// Moving average crossover system
const prices: number[] = bdpo.getClose(data);
const fastMA: number[] = bdpo.ema(prices, 12);
const slowMA: number[] = bdpo.ema(prices, 26);

// Plot fast MA in green using spline properties
bdpo.plot("splines", [fastMA], {
  splineColor: "#00E676",
  splineWidth: 2,
  skipNan: true
}, {
  precision: 4
});

// Plot slow MA in red using spline properties
bdpo.plot("splines", [slowMA], {
  splineColor: "#FF5722",
  splineWidth: 2,
  skipNan: true
}, {
  precision: 4
});

// Dynamic support/resistance levels
const highs: number[] = bdpo.getHigh(data);
const lows: number[] = bdpo.getLow(data);

// Rolling resistance level
const resistance: number[] = [];
for (let i = 0; i < highs.length; i++) {
  const periodHighs = highs.slice(Math.max(0, i - 20), i + 1);
  resistance.push(bdpo.max(periodHighs));
}

// Rolling support level
const support: number[] = [];
for (let i = 0; i < lows.length; i++) {
  const periodLows = lows.slice(Math.max(0, i - 20), i + 1);
  support.push(bdpo.min(periodLows));
}

bdpo.plot("splines", [resistance], {
  splineColor: "#FF1744",
  splineWidth: 2,
  skipNan: true
}, {
  precision: 4,
});

bdpo.plot("splines", [support], {
  splineColor: "#00E676",
  splineWidth: 2,
  skipNan: true
}, {
  precision: 4,
});

Histogram Plots

Histogram Plot Examples
Create oscillators, volume indicators, and momentum histograms
Properties Reference:

For detailed histogram properties, see the Histogram Properties section above. Key properties include histColorUp, histColorDw, histBarWidth, and histLineWidth.

Example Usage

// MACD histogram with proper histogram properties
const prices: number[] = bdpo.getClose(data);
const macd: any = bdpo.macd(prices, 12, 26, 9);
const histogram: number[] = macd.histogram;

bdpo.plot("histogram", [histogram], {
  histColorUp: "#4CAF50",
  histColorDw: "#F44336",
  histColorSemiUp: "#8BC34A",
  histColorSemiDw: "#FFAB91",
  histBarWidth: 6,
  histLineWidth: 1
}, {
  precision: 4,
});

// Volume profile histogram
const volumes: number[] = bdpo.getVolume(data);
const volumeMA: number[] = bdpo.sma(volumes, 20);

// Relative volume (current vs average)
const relativeVolume: number[] = volumes.map((vol, i) => 
  volumeMA[i] > 0 ? (vol / volumeMA[i] - 1) * 100 : 0
);

bdpo.plot("histogram", [relativeVolume], {
  histColorUp: "#2196F3",
  histColorDw: "#FF5722",
  histColorValue: "#FF9800",
  histBarWidth: 4,
  histLineWidth: 1
}, {
  precision: 1,
});

// Momentum oscillator with zero line
const rsi: number[] = bdpo.rsi(prices, 14);
const rsiCentered: number[] = rsi.map(value => value - 50); // Center around 0

bdpo.plot("histogram", [rsiCentered], {
  histColorUp: "#4CAF50",
  histColorDw: "#F44336",
  histColorSemiUp: "#8BC34A",
  histColorSemiDw: "#FFAB91",
  histBarWidth: 5,
  histLineWidth: 1
}, {
  precision: 2,
});

Signal and Arrow Plots

Signal Plot Examples
Create buy/sell signals, alerts, and directional indicators using "arrowTrades" and "trades" plot types
Properties Reference:

For detailed signal properties, see the ArrowTrades Properties and Trades Properties sections above. Key properties include arrowBuyColor, arrowSellColor, arrowSize, tradeBuyColor, tradeSellColor, and tradeRadius.

Example Usage

// Moving average crossover signals with arrow trades
const prices: number[] = bdpo.getClose(data);
const fastMA: number[] = bdpo.ema(prices, 12);
const slowMA: number[] = bdpo.ema(prices, 26);

// Generate crossover signals
const buySignals: number[] = [];
const sellSignals: number[] = [];

for (let i = 1; i < prices.length; i++) {
  // Buy signal: fast MA crosses above slow MA
  if (fastMA[i] > slowMA[i] && fastMA[i-1] <= slowMA[i-1]) {
    buySignals.push(prices[i]);
  } else {
    buySignals.push(null);
  }
  
  // Sell signal: fast MA crosses below slow MA
  if (fastMA[i] < slowMA[i] && fastMA[i-1] >= slowMA[i-1]) {
    sellSignals.push(prices[i]);
  } else {
    sellSignals.push(null);
  }
}

// Plot buy signals with arrow trades
bdpo.plot("arrowTrades", [buySignals], {
  arrowBuyColor: "#4CAF50",
  arrowSellColor: "#F44336",
  arrowSize: 8,
  arrowShowLabels: true,
  arrowMarkerOutline: true,
  arrowOutlineWidth: 2
}, {
  precision: 4,
});

// Plot sell signals with arrow trades
bdpo.plot("arrowTrades", [sellSignals], {
  arrowBuyColor: "#4CAF50",
  arrowSellColor: "#F44336",
  arrowSize: 8,
  arrowShowLabels: true,
  arrowMarkerOutline: true,
  arrowOutlineWidth: 2
}, {
  precision: 4,
});

// Trade execution markers using trades plot type
const tradeData: any[] = [
  // Buy trade at index 50
  { type: 'buy', price: 100.50, index: 50 },
  // Sell trade at index 75
  { type: 'sell', price: 105.25, index: 75 }
];

// Convert trade data to plot format
const tradeMarkers: number[] = new Array(prices.length).fill(null);
tradeData.forEach(trade => {
  tradeMarkers[trade.index] = trade.price;
});

bdpo.plot("trades", [tradeMarkers], {
  tradeBuyColor: "#08b2c6",
  tradeSellColor: "#e42633",
  tradeRadius: 5,
  tradeShowLabels: true,
  tradeMarkerOutline: true,
  tradeOutlineWidth: 3
}, {
  title: "Trade Executions"
});
for (let i = 1; i < fastMA.length; i++) { const currentFast = fastMA[i]; const currentSlow = slowMA[i]; const prevFast = fastMA[i - 1]; const prevSlow = slowMA[i - 1]; // Bullish crossover if (prevFast <= prevSlow && currentFast > currentSlow) { buySignals[i] = prices[i]; } else { buySignals[i] = null; } // Bearish crossover if (prevFast >= prevSlow && currentFast < currentSlow) { sellSignals[i] = prices[i]; } else { sellSignals[i] = null; } } // Plot buy signals bdpo.plot("arrowTrades", { high: bdpo.getHigh(data), low: bdpo.getLow(data), close: prices, trades: buySignals.map((signal, i) => signal ? [1, "BUY", false] : null).filter(Boolean) }, { arrowBuyColor: "#4CAF50", arrowSellColor: "#F44336", arrowSize: 8, arrowShowLabels: true, arrowMarkerOutline: true, arrowOutlineWidth: 2 }, { title: "Buy Signals" }); // Plot sell signals bdpo.plot("arrowTrades", { high: bdpo.getHigh(data), low: bdpo.getLow(data), close: prices, trades: sellSignals.map((signal, i) => signal ? [-1, "SELL", false] : null).filter(Boolean) }, { arrowBuyColor: "#4CAF50", arrowSellColor: "#F44336", arrowSize: 8, arrowShowLabels: true, arrowMarkerOutline: true, arrowOutlineWidth: 2 }, { title: "Sell Signals" }); // Support/Resistance breakout alerts const highs: number[] = bdpo.getHigh(data); const lows: number[] = bdpo.getLow(data); const breakoutSignals: number[] = []; for (let i = 20; i < prices.length; i++) { const recentHigh = bdpo.max(highs.slice(i - 20, i)); const recentLow = bdpo.min(lows.slice(i - 20, i)); // Breakout above resistance if (prices[i] > recentHigh) { breakoutSignals[i] = prices[i]; } // Breakdown below support else if (prices[i] < recentLow) { breakoutSignals[i] = prices[i]; } else { breakoutSignals[i] = null; } } bdpo.plot("trades", breakoutSignals.map((signal, i) => signal ? [1, signal, "BREAKOUT"] : null ).filter(Boolean), { tradeBuyColor: "#FF9800", tradeSellColor: "#FF9800", tradeRadius: 6, tradeShowLabels: true, tradeMarkerOutline: true, tradeOutlineWidth: 2 }, { title: "Breakout Alerts" });

Advanced Plotting Techniques

Advanced Plot Examples
Complex visualizations, multi-layer plots, and custom indicators

Advanced Features

Feature Description Usage
Multi-layer Overlays Combine multiple plot types Layer lines, histograms, and signals
Conditional Styling Dynamic colors based on conditions Trend-aware coloring systems
Data Transformation Pre-process data before plotting Normalization, smoothing, scaling
Performance Optimization Efficient plotting for large datasets Data sampling and caching

Example Usage

// Advanced market structure analysis
const prices: number[] = bdpo.getClose(data);
const highs: number[] = bdpo.getHigh(data);
const lows: number[] = bdpo.getLow(data);
const volumes: number[] = bdpo.getVolume(data);

// 1. Trend strength indicator
const trendStrength: number[] = [];
const period = 20;

for (let i = period; i < prices.length; i++) {
  const periodPrices = prices.slice(i - period, i + 1);
  const linearReg = calculateLinearRegression(periodPrices);
  const slope = linearReg.slope;
  const rSquared = linearReg.rSquared;
  
  // Combine slope and correlation for trend strength
  trendStrength[i] = slope * rSquared * 100;
}

// Plot trend strength with conditional colors
const strengthColors = trendStrength.map(strength => {
  if (strength > 0.5) return "#4CAF50";      // Strong uptrend
  if (strength > 0.1) return "#8BC34A";      // Weak uptrend
  if (strength < -0.5) return "#F44336";     // Strong downtrend
  if (strength < -0.1) return "#FF5722";     // Weak downtrend
  return "#9E9E9E";                          // Sideways
});

bdpo.plot("histogram", [trendStrength], {
  histColorUp: "#4CAF50",
  histColorDw: "#F44336",
  histColorSemiUp: "#8BC34A",
  histColorSemiDw: "#FFAB91",
  histBarWidth: 4,
  histLineWidth: 1
}, {
  title: "Trend Strength",
  precision: 2
});

// 2. Volume-weighted price levels
const vwap: number[] = [];
const vwapBands: { upper: number[], lower: number[] } = { upper: [], lower: [] };

for (let i = 0; i < prices.length; i++) {
  // Calculate VWAP for current session
  const sessionStart = Math.max(0, i - 100); // 100-period session
  const sessionPrices = prices.slice(sessionStart, i + 1);
  const sessionVolumes = volumes.slice(sessionStart, i + 1);
  
  let totalVolumePrice = 0;
  let totalVolume = 0;
  
  for (let j = 0; j < sessionPrices.length; j++) {
    totalVolumePrice += sessionPrices[j] * sessionVolumes[j];
    totalVolume += sessionVolumes[j];
  }
  
  vwap[i] = totalVolume > 0 ? totalVolumePrice / totalVolume : prices[i];
  
  // Calculate standard deviation bands
  const variance = sessionPrices.reduce((sum, price, idx) => {
    const weightedPrice = price * sessionVolumes[idx];
    return sum + Math.pow(weightedPrice - vwap[i] * sessionVolumes[idx], 2);
  }, 0) / totalVolume;
  
  const stdDev = Math.sqrt(variance);
  vwapBands.upper[i] = vwap[i] + stdDev * 2;
  vwapBands.lower[i] = vwap[i] - stdDev * 2;
}

// Plot VWAP and bands
bdpo.plot("splines", [vwap], {
  splineColor: "#FF9800",
  splineWidth: 2,
  skipNan: true
}, {
  title: "VWAP",
  precision: 4
});

bdpo.plot("splines", [vwapBands.upper], {
  splineColor: "#FF9800",
  splineWidth: 1,
  skipNan: true
}, {
  title: "VWAP Upper Band"
});

bdpo.plot("splines", [vwapBands.lower], {
  splineColor: "#FF9800",
  splineWidth: 1,
  skipNan: true
}, {
  title: "VWAP Lower Band"
});

// 3. Market regime detection
const volatility: number[] = bdpo.atr(highs, lows, prices, 14);
const avgVol: number[] = bdpo.sma(volatility, 50);
const volRatio: number[] = volatility.map((vol, i) => 
  avgVol[i] > 0 ? vol / avgVol[i] : 1
);

// Regime classification
const regimeSignals: number[] = volRatio.map((ratio, i) => {
  if (ratio > 1.5) return 2;    // High volatility regime
  if (ratio < 0.7) return -2;   // Low volatility regime
  return 0;                     // Normal regime
});

bdpo.plot("histogram", [regimeSignals], {
  conditionalColors: {
    2: "#F44336",   // High vol (red)
    0: "#9E9E9E",   // Normal (gray)
    "-2": "#4CAF50" // Low vol (green)
  },
  opacity: 0.6
}, {
  title: "Market Regime",
  precision: 0
});

// Helper function for linear regression
function calculateLinearRegression(data: number[]): { slope: number, rSquared: number } {
  const n = data.length;
  const x = Array.from({ length: n }, (_, i) => i);
  const y = data;
  
  const sumX = bdpo.sum(x);
  const sumY = bdpo.sum(y);
  const sumXY = bdpo.sum(x.map((xi, i) => xi * y[i]));
  const sumXX = bdpo.sum(x.map(xi => xi * xi));
  const sumYY = bdpo.sum(y.map(yi => yi * yi));
  
  const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
  const intercept = (sumY - slope * sumX) / n;
  
  // Calculate R-squared
  const yMean = sumY / n;
  const ssRes = bdpo.sum(y.map((yi, i) => Math.pow(yi - (slope * x[i] + intercept), 2)));
  const ssTot = bdpo.sum(y.map(yi => Math.pow(yi - yMean, 2)));
  const rSquared = 1 - (ssRes / ssTot);
  
  return { slope, rSquared };
}
Pro Tips:
  • Use consistent color schemes across related plots for better visual coherence
  • Implement conditional coloring to highlight important market conditions
  • Layer multiple plot types (lines + histograms + signals) for comprehensive analysis
  • Optimize plot data by filtering null values and using appropriate precision settings
  • Consider chart readability - avoid overplotting with too many overlays
  • Use opacity and line styles to differentiate between primary and secondary indicators
  • Test plot performance with large datasets and implement data sampling if needed
  • Combine plotting with logging to track indicator values and signal generation

Candlestick Functions

OHLCV data extraction and candlestick analysis functions

Note: Candlestick functions provide access to OHLCV data extraction, timeframe management, and candlestick pattern analysis. All functions work with the standard candlestick data format: [timestamp, open, high, low, close, volume].

Data Access Functions

bdpo.getTimeframeData(timeframe)
Get candlestick data for a specific timeframe

Parameters

Parameter Type Description
data Object Multi-timeframe data object
timeframe String|Number Target timeframe (e.g., "5m", "1h", "1d") or 0/"current" for chart timeframe

Returns

Array - Candlestick data array for the specified timeframe

Example Usage

// Get different timeframe data
const chartData: any[] = bdpo.getTimeframeData("current");
const hourlyData: any[] = bdpo.getTimeframeData("1h");
const dailyData: any[] = bdpo.getTimeframeData("1d");

// Use in strategy
if (chartData && chartData.length > 0) {
  const currentPrice: number = chartData[chartData.length - 1][4]; // Last close
  bdpo.log(`Current price: ${currentPrice}`);
}
bdpo.getChartData()
Get the current chart timeframe data (index 0)

Parameters

Parameter Type Description
data Object Multi-timeframe data object

Returns

Array - Current chart timeframe candlestick data

Example Usage

// Get current chart data
const chartData: any[] = bdpo.getChartData(data);

// Process current timeframe candles
if (chartData && chartData.length >= 50) {
  const sma20: number[] = bdpo.sma(bdpo.getClose(chartData), 20);
  const lastSMA: number = sma20[sma20.length - 1];
  
  bdpo.log(`Current SMA20: ${lastSMA.toFixed(4)}`);
}
bdpo.getAvailableTimeframes(data)
Get list of all available timeframes from the data object

Parameters

Parameter Type Description
data Object Multi-timeframe data object

Returns

Array<String> - Array of available timeframe keys

Example Usage

// Check available timeframes
const timeframes: string[] = bdpo.getAvailableTimeframes(data);
bdpo.log(`Available timeframes: ${timeframes.join(", ")}`);

// Dynamically process multiple timeframes
timeframes.forEach((tf: string) => {
  const tfData: any[] = bdpo.getTimeframeData(tf);
  const closes: number[] = bdpo.getClose(tfData);
  const lastClose: number = closes[closes.length - 1];
  
  bdpo.log(`${tf} last close: ${lastClose}`);
});

OHLCV Extraction Functions

bdpo.getTimestamps(candleData)
Extract timestamps from candlestick data

Parameters

Parameter Type Description
candleData Array Array of candlestick data [timestamp, open, high, low, close, volume]

Returns

Array<Number> - Array of timestamps

Example Usage

// Extract timestamps
const chartData: any[] = bdpo.getChartData(data);
const timestamps: number[] = bdpo.getTimestamps(chartData);

// Convert to dates and analyze time gaps
const dates: Date[] = timestamps.map((ts: number) => bdpo.timestampToDate(ts));
const lastCandleTime: Date = dates[dates.length - 1];

bdpo.log(`Last candle time: ${lastCandleTime.toISOString()}`);
bdpo.getOpen(candleData)
Extract open prices from candlestick data

Parameters

Parameter Type Description
candleData Array Array of candlestick data [timestamp, open, high, low, close, volume]

Returns

Array<Number> - Array of open prices

Example Usage

// Extract open prices
const chartData: any[] = bdpo.getChartData(data);
const opens: number[] = bdpo.getOpen(chartData);

// Calculate opening gap
const lastOpen: number = opens[opens.length - 1];
const prevClose: number = bdpo.getClose(chartData)[chartData.length - 2];
const gap: number = ((lastOpen - prevClose) / prevClose) * 100;

bdpo.log(`Opening gap: ${gap.toFixed(2)}%`);
bdpo.getHigh(candleData)
Extract high prices from candlestick data

Parameters

Parameter Type Description
candleData Array Array of candlestick data [timestamp, open, high, low, close, volume]

Returns

Array<Number> - Array of high prices

Example Usage

// Extract high prices
const chartData: any[] = bdpo.getChartData(data);
const highs: number[] = bdpo.getHigh(chartData);

// Find recent high
const recentHigh: number = bdpo.getHighestHigh(chartData, 20);
const currentPrice: number = bdpo.getClose(chartData)[chartData.length - 1];
const distanceFromHigh: number = ((currentPrice - recentHigh) / recentHigh) * 100;

bdpo.log(`Distance from 20-period high: ${distanceFromHigh.toFixed(2)}%`);
bdpo.getLow(candleData)
Extract low prices from candlestick data

Parameters

Parameter Type Description
candleData Array Array of candlestick data [timestamp, open, high, low, close, volume]

Returns

Array<Number> - Array of low prices

Example Usage

// Extract low prices
const chartData: any[] = bdpo.getChartData(data);
const lows: number[] = bdpo.getLow(chartData);

// Find support level
const recentLow: number = bdpo.getLowestLow(chartData, 50);
const currentPrice: number = bdpo.getClose(chartData)[chartData.length - 1];
const supportLevel: number = recentLow * 1.02; // 2% above recent low

if (currentPrice <= supportLevel) {
  bdpo.log("Price near support level");
}
bdpo.getClose(candleData)
Extract close prices from candlestick data

Parameters

Parameter Type Description
candleData Array Array of candlestick data [timestamp, open, high, low, close, volume]

Returns

Array<Number> - Array of close prices

Example Usage

// Extract close prices for technical analysis
const chartData: any[] = bdpo.getChartData(data);
const closes: number[] = bdpo.getClose(chartData);

// Use with indicators
const sma20: number[] = bdpo.sma(closes, 20);
const ema12: number[] = bdpo.ema(closes, 12);
const rsi: number[] = bdpo.rsi(closes, 14);

// Current values
const currentClose: number = closes[closes.length - 1];
const currentSMA: number = sma20[sma20.length - 1];
const currentRSI: number = rsi[rsi.length - 1];

bdpo.log(`Close: ${currentClose}, SMA20: ${currentSMA.toFixed(4)}, RSI: ${currentRSI.toFixed(2)}`);
bdpo.getVolume(candleData)
Extract volume data from candlestick data

Parameters

Parameter Type Description
candleData Array Array of candlestick data [timestamp, open, high, low, close, volume]

Returns

Array<Number> - Array of volume values

Example Usage

// Extract volume for analysis
const chartData: any[] = bdpo.getChartData(data);
const volumes: number[] = bdpo.getVolume(chartData);

// Calculate volume moving average
const volumeSMA: number[] = bdpo.sma(volumes, 20);
const currentVolume: number = volumes[volumes.length - 1];
const avgVolume: number = volumeSMA[volumeSMA.length - 1];

// Volume spike detection
const volumeRatio: number = currentVolume / avgVolume;
if (volumeRatio > 2.0) {
  bdpo.log(`High volume spike detected: ${volumeRatio.toFixed(2)}x average`);
}

Data Filtering and Slicing Functions

bdpo.getLastCandles(candleData, count)
Get the last N candles from the data

Parameters

Parameter Type Description
candleData Array Array of candlestick data
count Number Number of candles to retrieve from the end

Returns

Array - Last N candles

Example Usage

// Get recent data for analysis
const chartData: any[] = bdpo.getChartData(data);
const last50Candles: any[] = bdpo.getLastCandles(chartData, 50);

// Calculate short-term indicators
const recentCloses: number[] = bdpo.getClose(last50Candles);
const shortRSI: number[] = bdpo.rsi(recentCloses, 14);
const recentHigh: number = bdpo.getHighestHigh(last50Candles, 20);

bdpo.log(`Recent RSI: ${shortRSI[shortRSI.length - 1].toFixed(2)}`);
bdpo.getFirstCandles(candleData, count)
Get the first N candles from the data

Parameters

Parameter Type Description
candleData Array Array of candlestick data
count Number Number of candles to retrieve from the beginning

Returns

Array - First N candles

Example Usage

// Analyze historical data
const chartData: any[] = bdpo.getChartData(data);
const first100Candles: any[] = bdpo.getFirstCandles(chartData, 100);

// Calculate historical volatility
const historicalCloses: number[] = bdpo.getClose(first100Candles);
const returns: number[] = [];
for (let i = 1; i < historicalCloses.length; i++) {
  const dailyReturn: number = (historicalCloses[i] - historicalCloses[i-1]) / historicalCloses[i-1];
  returns.push(dailyReturn);
}

bdpo.log(`Historical period analyzed: ${first100Candles.length} candles`);
bdpo.getCandlesByTimestamp(candleData, startTimestamp, endTimestamp)
Get candles within a specific timestamp range

Parameters

Parameter Type Description
candleData Array Array of candlestick data
startTimestamp Number Start timestamp (inclusive)
endTimestamp Number End timestamp (inclusive)

Returns

Array - Candles within the timestamp range

Example Usage

// Analyze specific time period
const chartData: any[] = bdpo.getChartData(data);
const now: number = Date.now();
const yesterday: number = now - (24 * 60 * 60 * 1000);

const recentCandles: any[] = bdpo.getCandlesByTimestamp(chartData, yesterday, now);
const periodCloses: number[] = bdpo.getClose(recentCandles);

// Calculate period performance
if (periodCloses.length > 1) {
  const periodReturn: number = ((periodCloses[periodCloses.length - 1] - periodCloses[0]) / periodCloses[0]) * 100;
  bdpo.log(`24h return: ${periodReturn.toFixed(2)}%`);
}
bdpo.getLastCandle(candleData)
Get the most recent (last) candle

Parameters

Parameter Type Description
candleData Array Array of candlestick data

Returns

Array|null - Last candle [timestamp, open, high, low, close, volume] or null if no data

Example Usage

// Get current candle information
const chartData: any[] = bdpo.getChartData(data);
const lastCandle: any[] | null = bdpo.getLastCandle(chartData);

if (lastCandle) {
  const [timestamp, open, high, low, close, volume] = lastCandle;
  const bodySize: number = Math.abs(close - open);
  const range: number = high - low;
  const bodyPercentage: number = (bodySize / range) * 100;
  
  bdpo.log(`Current candle - O:${open} H:${high} L:${low} C:${close} V:${volume}`);
  bdpo.log(`Body: ${bodyPercentage.toFixed(1)}% of range`);
}
bdpo.getFirstCandle(candleData)
Get the first candle from the data

Parameters

Parameter Type Description
candleData Array Array of candlestick data

Returns

Array|null - First candle [timestamp, open, high, low, close, volume] or null if no data

Example Usage

// Compare first and last candles
const chartData: any[] = bdpo.getChartData(data);
const firstCandle: any[] | null = bdpo.getFirstCandle(chartData);
const lastCandle: any[] | null = bdpo.getLastCandle(chartData);

if (firstCandle && lastCandle) {
  const totalReturn: number = ((lastCandle[4] - firstCandle[4]) / firstCandle[4]) * 100;
  const startDate: Date = bdpo.timestampToDate(firstCandle[0]);
  const endDate: Date = bdpo.timestampToDate(lastCandle[0]);
  
  bdpo.log(`Total return from ${startDate.toDateString()} to ${endDate.toDateString()}: ${totalReturn.toFixed(2)}%`);
}

Data Analysis Functions

bdpo.getHighestHigh(candleData, period?)
Get the highest high over a specified period

Parameters

Parameter Type Description
candleData Array Array of candlestick data
period Number Optional. Number of periods to look back. If not provided, looks at all data

Returns

Number|null - Highest high value or null if no data

Example Usage

// Find resistance levels
const chartData: any[] = bdpo.getChartData(data);
const high20: number = bdpo.getHighestHigh(chartData, 20);
const high50: number = bdpo.getHighestHigh(chartData, 50);
const allTimeHigh: number = bdpo.getHighestHigh(chartData);

const currentPrice: number = bdpo.getClose(chartData)[chartData.length - 1];

// Distance from highs
const distanceFrom20High: number = ((currentPrice - high20) / high20) * 100;
const distanceFrom50High: number = ((currentPrice - high50) / high50) * 100;

bdpo.log(`20-period high: ${high20} (${distanceFrom20High.toFixed(1)}% away)`);
bdpo.log(`50-period high: ${high50} (${distanceFrom50High.toFixed(1)}% away)`);
bdpo.getLowestLow(candleData, period?)
Get the lowest low over a specified period

Parameters

Parameter Type Description
candleData Array Array of candlestick data
period Number Optional. Number of periods to look back. If not provided, looks at all data

Returns

Number|null - Lowest low value or null if no data

Example Usage

// Find support levels
const chartData: any[] = bdpo.getChartData(data);
const low20: number = bdpo.getLowestLow(chartData, 20);
const low50: number = bdpo.getLowestLow(chartData, 50);
const allTimeLow: number = bdpo.getLowestLow(chartData);

const currentPrice: number = bdpo.getClose(chartData)[chartData.length - 1];

// Support level analysis
const supportDistance20: number = ((currentPrice - low20) / low20) * 100;
const supportDistance50: number = ((currentPrice - low50) / low50) * 100;

if (supportDistance20 < 5) {
  bdpo.log("Price approaching 20-period support level");
}

bdpo.log(`20-period low: ${low20} (+${supportDistance20.toFixed(1)}% above)`);
bdpo.getPriceRange(candleData)
Get the price range (high - low) for each candle

Parameters

Parameter Type Description
candleData Array Array of candlestick data

Returns

Array<Number> - Array of price ranges

Example Usage

// Analyze volatility through price ranges
const chartData: any[] = bdpo.getChartData(data);
const ranges: number[] = bdpo.getPriceRange(chartData);

// Calculate average range and volatility
const avgRange: number[] = bdpo.sma(ranges, 20);
const currentRange: number = ranges[ranges.length - 1];
const avgCurrentRange: number = avgRange[avgRange.length - 1];

// Volatility expansion/contraction
const rangeRatio: number = currentRange / avgCurrentRange;

if (rangeRatio > 1.5) {
  bdpo.log("High volatility - range expansion detected");
} else if (rangeRatio < 0.5) {
  bdpo.log("Low volatility - range contraction detected");
}
bdpo.getBodySize(candleData)
Get the body size (|close - open|) for each candle

Parameters

Parameter Type Description
candleData Array Array of candlestick data

Returns

Array<Number> - Array of body sizes

Example Usage

// Analyze candle strength through body size
const chartData: any[] = bdpo.getChartData(data);
const bodySizes: number[] = bdpo.getBodySize(chartData);
const ranges: number[] = bdpo.getPriceRange(chartData);

// Calculate body-to-range ratios
const bodyRatios: number[] = bodySizes.map((body: number, i: number) => 
  ranges[i] > 0 ? (body / ranges[i]) * 100 : 0
);

const lastBodyRatio: number = bodyRatios[bodyRatios.length - 1];

// Candle strength analysis
if (lastBodyRatio > 70) {
  bdpo.log("Strong candle - large body relative to range");
} else if (lastBodyRatio < 30) {
  bdpo.log("Weak candle - small body, likely indecision");
}
bdpo.getUpperShadow(candleData)
Get the upper shadow (high - max(open, close)) for each candle

Parameters

Parameter Type Description
candleData Array Array of candlestick data

Returns

Array<Number> - Array of upper shadow lengths

Example Usage

// Analyze rejection at higher prices
const chartData: any[] = bdpo.getChartData(data);
const upperShadows: number[] = bdpo.getUpperShadow(chartData);
const ranges: number[] = bdpo.getPriceRange(chartData);

// Calculate shadow-to-range ratios
const shadowRatios: number[] = upperShadows.map((shadow: number, i: number) => 
  ranges[i] > 0 ? (shadow / ranges[i]) * 100 : 0
);

const lastShadowRatio: number = shadowRatios[shadowRatios.length - 1];

// Rejection pattern detection
if (lastShadowRatio > 60) {
  bdpo.log("Strong upper rejection - potential resistance");
  
  // Check if it's a shooting star pattern
  const bodySizes: number[] = bdpo.getBodySize(chartData);
  const lastBodyRatio: number = (bodySizes[bodySizes.length - 1] / ranges[ranges.length - 1]) * 100;
  
  if (lastBodyRatio < 30) {
    bdpo.log("Shooting star pattern detected");
  }
}
bdpo.getLowerShadow(candleData)
Get the lower shadow (min(open, close) - low) for each candle

Parameters

Parameter Type Description
candleData Array Array of candlestick data

Returns

Array<Number> - Array of lower shadow lengths

Example Usage

// Analyze buying pressure and support
const chartData: any[] = bdpo.getChartData(data);
const lowerShadows: number[] = bdpo.getLowerShadow(chartData);
const ranges: number[] = bdpo.getPriceRange(chartData);

// Calculate lower shadow ratios
const shadowRatios: number[] = lowerShadows.map((shadow: number, i: number) => 
  ranges[i] > 0 ? (shadow / ranges[i]) * 100 : 0
);

const lastShadowRatio: number = shadowRatios[shadowRatios.length - 1];

// Support and hammer pattern detection
if (lastShadowRatio > 60) {
  bdpo.log("Strong lower rejection - potential support");
  
  // Check for hammer pattern
  const bodySizes: number[] = bdpo.getBodySize(chartData);
  const lastBodyRatio: number = (bodySizes[bodySizes.length - 1] / ranges[ranges.length - 1]) * 100;
  
  if (lastBodyRatio < 30) {
    bdpo.log("Hammer pattern detected - potential reversal");
  }
}
bdpo.isBullish(candleData)
Check if each candle is bullish (close > open)

Parameters

Parameter Type Description
candleData Array Array of candlestick data

Returns

Array<Boolean> - Array of boolean values indicating bullish candles

Example Usage

// Analyze bullish momentum
const chartData: any[] = bdpo.getChartData(data);
const bullishCandles: boolean[] = bdpo.isBullish(chartData);

// Count consecutive bullish candles
let consecutiveBullish: number = 0;
for (let i = bullishCandles.length - 1; i >= 0; i--) {
  if (bullishCandles[i]) {
    consecutiveBullish++;
  } else {
    break;
  }
}

// Calculate bullish percentage over last 20 candles
const last20: boolean[] = bullishCandles.slice(-20);
const bullishCount: number = last20.filter(Boolean).length;
const bullishPercentage: number = (bullishCount / last20.length) * 100;

bdpo.log(`Consecutive bullish candles: ${consecutiveBullish}`);
bdpo.log(`Bullish momentum (20-period): ${bullishPercentage.toFixed(1)}%`);
bdpo.isBearish(candleData)
Check if each candle is bearish (close < open)

Parameters

Parameter Type Description
candleData Array Array of candlestick data

Returns

Array<Boolean> - Array of boolean values indicating bearish candles

Example Usage

// Analyze bearish pressure
const chartData: any[] = bdpo.getChartData(data);
const bearishCandles: boolean[] = bdpo.isBearish(chartData);

// Count consecutive bearish candles
let consecutiveBearish: number = 0;
for (let i = bearishCandles.length - 1; i >= 0; i--) {
  if (bearishCandles[i]) {
    consecutiveBearish++;
  } else {
    break;
  }
}

// Calculate bearish pressure
const last20: boolean[] = bearishCandles.slice(-20);
const bearishCount: number = last20.filter(Boolean).length;
const bearishPercentage: number = (bearishCount / last20.length) * 100;

// Risk management based on bearish momentum
if (bearishPercentage > 70 && consecutiveBearish >= 3) {
  bdpo.log("Strong bearish momentum - consider defensive positioning");
}

bdpo.log(`Consecutive bearish candles: ${consecutiveBearish}`);

Data Transformation Functions

bdpo.timestampToDate(timestamp)
Convert timestamp to Date object

Parameters

Parameter Type Description
timestamp Number Unix timestamp to convert

Returns

Date - JavaScript Date object

Example Usage

// Convert timestamps for time-based analysis
const chartData: any[] = bdpo.getChartData(data);
const lastCandle: any[] | null = bdpo.getLastCandle(chartData);

if (lastCandle) {
  const candleDate: Date = bdpo.timestampToDate(lastCandle[0]);
  const now: Date = new Date();
  const ageMinutes: number = (now.getTime() - candleDate.getTime()) / (1000 * 60);
  
  bdpo.log(`Last candle date: ${candleDate.toISOString()}`);
  bdpo.log(`Candle age: ${ageMinutes.toFixed(1)} minutes`);
  
  // Time-based trading rules
  const hour: number = candleDate.getHours();
  if (hour >= 9 && hour <= 16) {
    bdpo.log("Market hours - active trading period");
  } else {
    bdpo.log("After hours - reduced liquidity expected");
  }
}
Pro Tips:
  • Always validate candlestick data before processing to avoid runtime errors
  • Use data filtering functions to focus analysis on relevant time periods
  • Combine OHLCV extraction with technical indicators for comprehensive analysis
  • Shadow analysis can help identify reversal patterns and market sentiment
  • Monitor consecutive bullish/bearish patterns for momentum confirmation

Symbol Functions

Asset metadata and trading context functions

Note: Symbol functions provide access to asset metadata, trading context, and real-time market data for the current trading symbol. These functions work in both live trading and backtesting environments.

Asset Information

bdpo.getAsset()
Get the complete asset object with all metadata

Returns

Object - Complete asset object containing all available metadata

Example Usage

// Get complete asset information
const asset: any = bdpo.getAsset();

if (asset) {
  bdpo.log(`Asset Details:`);
  bdpo.log(`Symbol: ${asset.ticker}`);
  bdpo.log(`Exchange: ${asset.exchange}`);
  bdpo.log(`Type: ${asset.type}`);
  bdpo.log(`Contract Size: ${asset.contract_size}`);
  
  // Use asset properties for trading decisions
  if (asset.type === 'crypto') {
    // Crypto-specific logic
  } else if (asset.type === 'forex') {
    // Forex-specific logic
  }
}
bdpo.getSymbol()
Get the trading symbol/ticker (e.g., "BTCUSD", "EURUSD")

Returns

String|null - Trading symbol or null if not available

Example Usage

// Get current trading symbol
const symbol: string | null = bdpo.getSymbol();

if (symbol) {
  bdpo.log(`Trading: ${symbol}`);
  
  // Symbol-specific trading logic
  if (symbol.includes('BTC')) {
    // Bitcoin-related strategies
    const btcSpecificRiskManagement = true;
  } else if (symbol.includes('USD')) {
    // USD pair strategies
    const usdPairStrategy = true;
  }
  
  // Use symbol for logging and tracking
  bdpo.log(`${symbol}: Strategy initialized`);
}
bdpo.getSource()
Get the data feed source (e.g., "BLACKBULL", "BINANCE")

Returns

String|null - Data feed source or null if not available

Example Usage

// Get data source
const source: string | null = bdpo.getSource();

if (source) {
  bdpo.log(`Data source: ${source}`);
  
  // Source-specific adjustments
  if (source === 'BINANCE') {
    // Binance-specific considerations (crypto exchange)
    const cryptoMode = true;
  } else if (source === 'BLACKBULL') {
    // BlackBull-specific considerations (forex broker)
    const forexMode = true;
  }
  
  // Adjust strategy based on data source characteristics
  bdpo.log(`Strategy adapted for ${source} data feed`);
}
bdpo.getName()
Get the human-readable asset name (e.g., "Bitcoin / USD")

Returns

String|null - Human-readable asset name or null if not available

Example Usage

// Get descriptive asset name
const assetName: string | null = bdpo.getName();

if (assetName) {
  bdpo.log(`Trading: ${assetName}`);
  
  // Use for user-friendly logging and reports
  const symbol: string = bdpo.getSymbol();
  const performance = calculatePerformance();
  
  bdpo.log(`${assetName} (${symbol}) Performance: ${performance.toFixed(2)}%`);
}
bdpo.getType()
Get the asset type (e.g., "crypto", "forex", "stock")

Returns

String|null - Asset type or null if not available

Example Usage

// Get asset type for strategy adaptation
const assetType: string | null = bdpo.getType();

if (assetType) {
  bdpo.log(`Asset type: ${assetType}`);
  
  // Type-specific strategy configuration
  switch (assetType) {
    case 'crypto':
      // Cryptocurrency specific settings
      const volatilityMultiplier = 1.5;
      const tradingHours = '24/7';
      break;
      
    case 'forex':
      // Forex specific settings
      const volatilityMultiplier = 1.0;
      const tradingHours = 'weekdays';
      break;
      
    case 'stock':
      // Stock specific settings
      const volatilityMultiplier = 0.8;
      const tradingHours = 'market_hours';
      break;
      
    default:
      bdpo.log(`Unknown asset type: ${assetType}`);
  }
}

Currency Information

bdpo.getBaseAsset()
Get the base asset currency (e.g., "BTC" in "BTCUSD")

Returns

String|null - Base asset currency or null if not available

Example Usage

// Get base and quote assets
const baseAsset: string | null = bdpo.getBaseAsset();
const quoteAsset: string | null = bdpo.getQuoteAsset();

if (baseAsset && quoteAsset) {
  bdpo.log(`Trading pair: ${baseAsset}/${quoteAsset}`);
  
  // Currency-specific analysis
  if (baseAsset === 'BTC') {
    // Bitcoin correlation analysis
    const btcDominance = checkBitcoinDominance();
  } else if (baseAsset === 'EUR') {
    // Euro economic indicators
    const euroZoneData = getEuroZoneEconomicData();
  }
  
  // Position sizing based on base asset
  const positionSize = calculatePositionSize(baseAsset);
}
bdpo.getQuoteAsset()
Get the quote asset currency (e.g., "USD" in "BTCUSD")

Returns

String|null - Quote asset currency or null if not available

Example Usage

// Get quote asset for P&L calculations
const quoteAsset: string | null = bdpo.getQuoteAsset();

if (quoteAsset) {
  bdpo.log(`Profit/Loss calculated in: ${quoteAsset}`);
  
  // Quote asset considerations
  if (quoteAsset === 'USD') {
    // US Dollar considerations
    const dollarStrengthIndex = getDollarIndex();
  } else if (quoteAsset === 'JPY') {
    // Japanese Yen considerations
    const yenCarryTrade = analyzeCarryTrade();
  }
  
  // Account currency conversion if needed
  const accountCurrency = getAccountCurrency();
  if (accountCurrency !== quoteAsset) {
    const conversionRate = getConversionRate(quoteAsset, accountCurrency);
    bdpo.log(`Conversion rate ${quoteAsset}/${accountCurrency}: ${conversionRate}`);
  }
}

Trading Specifications

bdpo.getContractSize()
Get the contract size for the asset

Returns

Number|null - Contract size or null if not available

Example Usage

// Get contract size for position calculations
const contractSize: number | null = bdpo.getContractSize();

if (contractSize) {
  bdpo.log(`Contract size: ${contractSize}`);
  
  // Position value calculations
  const lotSize = 1.0;
  const currentPrice = bdpo.getAsk();
  const positionValue = lotSize * contractSize * currentPrice;
  
  bdpo.log(`Position value: ${positionValue.toFixed(2)}`);
  
  // Risk management based on contract size
  const accountBalance = bdpo.getBalance();
  const maxPositionPercent = 0.02; // 2% risk
  const maxLots = (accountBalance * maxPositionPercent) / (contractSize * currentPrice);
  
  bdpo.log(`Max recommended lots: ${maxLots.toFixed(2)}`);
}
bdpo.getMinQty() / bdpo.getMaxQty()
Get minimum and maximum allowed trading quantities

Returns

Number|null - Minimum/Maximum quantity or null if not available

Example Usage

// Get trading quantity limits
const minQty: number | null = bdpo.getMinQty();
const maxQty: number | null = bdpo.getMaxQty();

if (minQty !== null && maxQty !== null) {
  bdpo.log(`Trading limits: ${minQty} - ${maxQty} lots`);
  
  // Validate order size before placing trades
  function validateOrderSize(requestedLots: number): number {
    if (requestedLots < minQty) {
      bdpo.log(`Order size too small. Minimum: ${minQty}, adjusting to ${minQty}`);
      return minQty;
    }
    
    if (requestedLots > maxQty) {
      bdpo.log(`Order size too large. Maximum: ${maxQty}, adjusting to ${maxQty}`);
      return maxQty;
    }
    
    return requestedLots;
  }
  
  // Use in order placement
  const desiredLots = 5.0;
  const validatedLots = validateOrderSize(desiredLots);
  
  bdpo.orderSend({
    orderType: "BUY",
    lotSize: validatedLots
  });
}

Price Specifications

bdpo.getPip()
Get the pip value for price movements

Returns

Number|null - Pip value or null if not available

Example Usage

// Get pip value for calculations
const pipValue: number | null = bdpo.getPip();

if (pipValue) {
  bdpo.log(`Pip value: ${pipValue}`);
  
  // Calculate pip-based stop loss and take profit
  const currentPrice = bdpo.getAsk();
  const stopLossPips = 50;
  const takeProfitPips = 100;
  
  const stopLossPrice = currentPrice - (stopLossPips * (pipValue / 10));
  const takeProfitPrice = currentPrice + (takeProfitPips * (pipValue / 10));
  
  bdpo.log(`Stop Loss: ${stopLossPrice.toFixed(5)}`);
  bdpo.log(`Take Profit: ${takeProfitPrice.toFixed(5)}`);
  
  // Risk calculation in pips
  const riskInPips = Math.abs(currentPrice - stopLossPrice) / (pipValue / 10);
  bdpo.log(`Risk: ${riskInPips.toFixed(1)} pips`);
}
bdpo.getPrecision()
Get the decimal precision for prices

Returns

Number|null - Decimal precision or null if not available

Example Usage

// Get price precision for formatting
const precision: number | null = bdpo.getPrecision();

if (precision) {
  bdpo.log(`Price precision: ${precision} decimal places`);
  
  // Format prices correctly
  function formatPrice(price: number): string {
    return price.toFixed(precision);
  }
  
  const currentPrice = bdpo.getAsk();
  const formattedPrice = formatPrice(currentPrice);
  
  bdpo.log(`Current price: ${formattedPrice}`);
  
  // Ensure order prices match broker precision
  const calculatedStopLoss = 1.23456789;
  const preciseStopLoss = parseFloat(formatPrice(calculatedStopLoss));
  
  bdpo.orderSend({
    orderType: "BUYLIMIT",
    price: preciseStopLoss,
    lotSize: 1.0
  });
}
bdpo.getStepSize()
Get the minimum price step size

Returns

Number|null - Minimum price step or null if not available

Example Usage

// Get minimum price step for order placement
const stepSize: number | null = bdpo.getStepSize();

if (stepSize) {
  bdpo.log(`Minimum price step: ${stepSize}`);
  
  // Round prices to valid steps
  function roundToStepSize(price: number): number {
    return Math.round(price / stepSize) * stepSize;
  }
  
  // Example: Place limit order with valid price
  const currentPrice = bdpo.getAsk();
  const targetPrice = currentPrice * 1.02; // 2% above current
  const validPrice = roundToStepSize(targetPrice);
  
  bdpo.log(`Target: ${targetPrice}, Valid: ${validPrice}`);
  
  bdpo.orderSend({
    orderType: "BUYLIMIT",
    price: validPrice,
    lotSize: 1.0
  });
}

Real-Time Pricing

bdpo.getAsk()
Get current ask price (selling price for buy orders)

Returns

Number - Current ask price

Example Usage

// Get current ask price for buy orders
const askPrice: number = bdpo.getAsk();
const bidPrice: number = bdpo.getBid();

bdpo.log(`Ask: ${askPrice}, Bid: ${bidPrice}`);

// Use ask price for buy order calculations
const buyOrderPrice = askPrice;
const stopLoss = askPrice * 0.98; // 2% below ask
const takeProfit = askPrice * 1.04; // 4% above ask

// Place buy order at current ask
bdpo.orderSend({
  orderType: "BUY",
  lotSize: 1.0,
  stopLoss: stopLoss,
  takeProfit: takeProfit
});

// Monitor spread
const spread = askPrice - bidPrice;
const spreadPips = bdpo.getSpread();
bdpo.log(`Spread: ${spread.toFixed(5)} (${spreadPips.toFixed(1)} pips)`);
bdpo.getBid()
Get current bid price (selling price for sell orders)

Returns

Number - Current bid price

Example Usage

// Get current bid price for sell orders
const bidPrice: number = bdpo.getBid();
const askPrice: number = bdpo.getAsk();

bdpo.log(`Bid: ${bidPrice}, Ask: ${askPrice}`);

// Use bid price for sell order calculations
const sellOrderPrice = bidPrice;
const stopLoss = bidPrice * 1.02; // 2% above bid
const takeProfit = bidPrice * 0.96; // 4% below bid

// Place sell order at current bid
bdpo.orderSend({
  orderType: "SELL",
  lotSize: 1.0,
  stopLoss: stopLoss,
  takeProfit: takeProfit
});

// Calculate mid price
const midPrice = (askPrice + bidPrice) / 2;
bdpo.log(`Mid price: ${midPrice.toFixed(5)}`);
bdpo.getSpread()
Get current spread in pips

Returns

Number - Current spread in pips

Example Usage

// Monitor spread for optimal trade entry
const currentSpread: number = bdpo.getSpread();
const avgSpread = 2.5; // Historical average spread in pips

bdpo.log(`Current spread: ${currentSpread.toFixed(1)} pips`);

// Trade only when spread is reasonable
if (currentSpread <= avgSpread * 1.5) {
  bdpo.log("Spread acceptable for trading");
  
  // Continue with trade logic
  const askPrice = bdpo.getAsk();
  const bidPrice = bdpo.getBid();
  
  // Factor spread into profit targets
  const minProfitPips = currentSpread * 3; // 3x spread minimum
  const takeProfitDistance = minProfitPips * (bdpo.getPip() / 10);
  
  bdpo.orderSend({
    orderType: "BUY",
    lotSize: 1.0,
    takeProfit: askPrice + takeProfitDistance
  });
  
} else {
  bdpo.log(`Spread too wide: ${currentSpread.toFixed(1)} pips. Waiting for better conditions.`);
}

Exchange Information

bdpo.getExchange()
Get the exchange name (e.g., "BlackBull Markets")

Returns

String|null - Exchange name or null if not available

Example Usage

// Get exchange information
const exchange: string | null = bdpo.getExchange();

if (exchange) {
  bdpo.log(`Trading on: ${exchange}`);
  
  // Exchange-specific configurations
  switch (exchange.toLowerCase()) {
    case 'blackbull markets':
      // BlackBull specific settings
      const maxLeverage = 500;
      const supportsCrypto = true;
      break;
      
    case 'binance':
      // Binance specific settings
      const maxLeverage = 125;
      const cryptoOnly = true;
      break;
      
    case 'mt4':
    case 'mt5':
      // MetaTrader platform settings
      const forexFocus = true;
      break;
      
    default:
      bdpo.log(`Unknown exchange: ${exchange}`);
  }
  
  // Log exchange in trade records
  const symbol = bdpo.getSymbol();
  bdpo.log(`${symbol} @ ${exchange}: Strategy active`);
}
Pro Tips:
  • Always check symbol metadata before implementing trading logic
  • Use asset type to adapt strategy parameters for different markets
  • Monitor spread conditions to optimize trade entry timing
  • Validate order sizes against min/max quantity limits
  • Factor in pip values and precision for accurate price calculations
  • Consider exchange-specific characteristics in your strategy

Math Functions

Mathematical utilities and statistical analysis functions

Note: Math functions provide essential mathematical operations, statistical analysis, and array manipulations powered by the Math.js library. These functions enable complex calculations, data analysis, and mathematical transformations in your trading strategies.

Basic Statistics

bdpo.sum(array)
Calculate the sum of all elements in an array

Parameters

Parameter Type Description
array Array<Number> Array of numbers to sum

Returns

Number - Sum of all array elements

Example Usage

// Calculate total volume over last 10 periods
const volumes: number[] = bdpo.getVolume(data).slice(-10);
const totalVolume: number = bdpo.sum(volumes);

// Calculate cumulative returns
const returns: number[] = [];
const prices: number[] = bdpo.getClose(data);
for (let i = 1; i < prices.length; i++) {
  const dailyReturn: number = (prices[i] - prices[i-1]) / prices[i-1];
  returns.push(dailyReturn);
}
const totalReturn: number = bdpo.sum(returns);

bdpo.log(`Total volume (10 periods): ${totalVolume}`);
bdpo.log(`Cumulative return: ${(totalReturn * 100).toFixed(2)}%`);
bdpo.mean(array)
Calculate the arithmetic mean (average) of an array

Parameters

Parameter Type Description
array Array<Number> Array of numbers to average

Returns

Number - Arithmetic mean of the array

Example Usage

// Calculate average price over last 20 periods
const prices: number[] = bdpo.getClose(data).slice(-20);
const avgPrice: number = bdpo.mean(prices);

// Calculate average daily range
const highs: number[] = bdpo.getHigh(data);
const lows: number[] = bdpo.getLow(data);
const ranges: number[] = highs.map((high: number, i: number) => high - lows[i]);
const avgRange: number = bdpo.mean(ranges.slice(-30));

// Performance metrics
const currentPrice: number = prices[prices.length - 1];
const distanceFromAvg: number = ((currentPrice - avgPrice) / avgPrice) * 100;

bdpo.log(`Average price (20 periods): ${avgPrice.toFixed(4)}`);
bdpo.log(`Average range (30 periods): ${avgRange.toFixed(4)}`);
bdpo.log(`Distance from average: ${distanceFromAvg.toFixed(2)}%`);
bdpo.median(array)
Calculate the median (middle value) of an array

Parameters

Parameter Type Description
array Array<Number> Array of numbers to find median

Returns

Number - Median value of the array

Example Usage

// Find median price for support/resistance analysis
const prices: number[] = bdpo.getClose(data).slice(-50);
const medianPrice: number = bdpo.median(prices);

// Compare current price to median
const currentPrice: number = prices[prices.length - 1];
const meanPrice: number = bdpo.mean(prices);

// Median is less sensitive to outliers than mean
const meanVsMedian: number = meanPrice - medianPrice;

if (Math.abs(meanVsMedian) > medianPrice * 0.01) {
  bdpo.log("Significant skew detected in price distribution");
}

bdpo.log(`Median price: ${medianPrice.toFixed(4)}`);
bdpo.log(`Mean vs Median difference: ${meanVsMedian.toFixed(4)}`);
bdpo.variance(array) / bdpo.std(array)
Calculate variance and standard deviation for volatility analysis

Parameters

Parameter Type Description
array Array<Number> Array of numbers for statistical analysis

Returns

Number - Variance (squared standard deviation) or Standard deviation

Example Usage

// Calculate price volatility using returns
const prices: number[] = bdpo.getClose(data);
const returns: number[] = [];

// Calculate daily returns
for (let i = 1; i < prices.length; i++) {
  const dailyReturn: number = (prices[i] - prices[i-1]) / prices[i-1];
  returns.push(dailyReturn);
}

// Statistical measures of volatility
const returnVariance: number = bdpo.variance(returns.slice(-30));
const returnStdDev: number = bdpo.std(returns.slice(-30));
const annualizedVolatility: number = returnStdDev * Math.sqrt(252); // 252 trading days

// Risk assessment
const currentReturn: number = returns[returns.length - 1];
const zScore: number = Math.abs(currentReturn) / returnStdDev;

if (zScore > 2) {
  bdpo.log("Unusual price movement detected (>2 standard deviations)");
}

bdpo.log(`30-day volatility (annualized): ${(annualizedVolatility * 100).toFixed(2)}%`);
bdpo.log(`Current move Z-score: ${zScore.toFixed(2)}`);
bdpo.mode(array) / bdpo.mad(array) / bdpo.prod(array)
Additional statistical measures for comprehensive data analysis

Parameters

Function Parameters Description
mode(array) Array<Number> Most frequently occurring value(s)
mad(array) Array<Number> Median Absolute Deviation (robust volatility measure)
prod(array) Array<Number> Product of all elements in array

Example Usage

// Advanced statistical analysis for trading patterns
const prices: number[] = bdpo.getClose(data).slice(-100);

// Round prices to significant levels for mode analysis
const roundedPrices: number[] = prices.map((price: number) => {
  const decimalPlaces: number = 2; // Round to nearest cent
  return bdpo.round(price, decimalPlaces);
});

// Find most common price levels (support/resistance)
const priceMode: number[] = bdpo.mode(roundedPrices);
const modeLevel: number = Array.isArray(priceMode) ? priceMode[0] : priceMode;

// Robust volatility using MAD (less sensitive to outliers than std dev)
const returns: number[] = [];
for (let i = 1; i < prices.length; i++) {
  const dailyReturn: number = (prices[i] - prices[i-1]) / prices[i-1];
  returns.push(dailyReturn);
}

const madVolatility: number = bdpo.mad(returns);
const stdVolatility: number = bdpo.std(returns);
const robustnessRatio: number = madVolatility / stdVolatility; // Lower = more outliers

// Compound growth calculation using product
const periodReturns: number[] = returns.slice(-20).map((ret: number) => 1 + ret);
const compoundReturn: number = bdpo.prod(periodReturns) - 1;
const avgDailyReturn: number = bdpo.pow(bdpo.prod(periodReturns), 1/20) - 1;

// Risk-adjusted metrics
const currentPrice: number = prices[prices.length - 1];
const distanceFromMode: number = Math.abs(currentPrice - modeLevel) / currentPrice;
const madScore: number = Math.abs(returns[returns.length - 1]) / madVolatility;

// Price clustering analysis
const priceDistances: number[] = roundedPrices.map(price => Math.abs(price - modeLevel));
const avgDistanceFromMode: number = bdpo.mean(priceDistances);

bdpo.log(`Statistical Analysis (100 periods):`);
bdpo.log(`Most common price level: ${modeLevel.toFixed(4)}`);
bdpo.log(`Current distance from mode: ${(distanceFromMode * 100).toFixed(2)}%`);
bdpo.log(`MAD volatility: ${(madVolatility * 100).toFixed(3)}%`);
bdpo.log(`Std volatility: ${(stdVolatility * 100).toFixed(3)}%`);
bdpo.log(`Robustness ratio: ${robustnessRatio.toFixed(2)} (lower = more outliers)`);
bdpo.log(`20-day compound return: ${(compoundReturn * 100).toFixed(2)}%`);
bdpo.log(`Average daily return: ${(avgDailyReturn * 100).toFixed(3)}%`);
bdpo.log(`Current MAD score: ${madScore.toFixed(2)}`);
bdpo.min(array) / bdpo.max(array) / bdpo.range(array)
Find minimum, maximum, and range values for support/resistance analysis

Parameters

Parameter Type Description
array Array<Number> Array of numbers to analyze

Returns

Number - Minimum value, maximum value, or range (max - min)

Example Usage

// Find key levels for last 50 periods
const highs: number[] = bdpo.getHigh(data).slice(-50);
const lows: number[] = bdpo.getLow(data).slice(-50);
const closes: number[] = bdpo.getClose(data).slice(-50);

const resistance: number = bdpo.max(highs);
const support: number = bdpo.min(lows);
const priceRange: number = bdpo.range(closes);

const currentPrice: number = closes[closes.length - 1];

// Distance to key levels
const distanceToResistance: number = ((resistance - currentPrice) / currentPrice) * 100;
const distanceToSupport: number = ((currentPrice - support) / currentPrice) * 100;

// Range analysis
const avgClose: number = bdpo.mean(closes);
const rangePercentage: number = (priceRange / avgClose) * 100;

bdpo.log(`Resistance: ${resistance.toFixed(4)} (${distanceToResistance.toFixed(2)}% away)`);
bdpo.log(`Support: ${support.toFixed(4)} (${distanceToSupport.toFixed(2)}% away)`);
bdpo.log(`50-period range: ${rangePercentage.toFixed(2)}% of average price`);

Mathematical Functions

bdpo.abs(x) / bdpo.sqrt(x) / bdpo.pow(x, y)
Basic mathematical operations for calculations and transformations

Parameters

Function Parameters Description
abs(x) Number Returns absolute value
sqrt(x) Number Returns square root
pow(x, y) Number, Number Returns x raised to power y

Example Usage

// Calculate distance metrics
const currentPrice: number = bdpo.getClose(data)[data.length - 1];
const sma20: number[] = bdpo.sma(bdpo.getClose(data), 20);
const currentSMA: number = sma20[sma20.length - 1];

// Absolute distance from moving average
const distance: number = bdpo.abs(currentPrice - currentSMA);
const distancePercent: number = (distance / currentSMA) * 100;

// Squared distance for penalty functions
const squaredDistance: number = bdpo.pow(distance / currentSMA, 2);

// Root mean square calculation for volatility
const returns: number[] = [];
const prices: number[] = bdpo.getClose(data);
for (let i = 1; i < 21; i++) {
  const ret: number = (prices[prices.length - i] - prices[prices.length - i - 1]) / prices[prices.length - i - 1];
  returns.push(bdpo.pow(ret, 2));
}
const rmsVolatility: number = bdpo.sqrt(bdpo.mean(returns));

bdpo.log(`Distance from SMA: ${distancePercent.toFixed(2)}%`);
bdpo.log(`RMS Volatility: ${(rmsVolatility * 100).toFixed(2)}%`);
bdpo.log(x) / bdpo.log10(x) / bdpo.log2(x)
Logarithmic functions for return calculations and data transformations

Parameters

Function Parameters Description
log(x) Number Natural logarithm (base e)
log10(x) Number Base-10 logarithm
log2(x) Number Base-2 logarithm

Example Usage

// Calculate logarithmic returns (continuous compounding)
const prices: number[] = bdpo.getClose(data);
const logReturns: number[] = [];

for (let i = 1; i < prices.length; i++) {
  const logReturn: number = bdpo.log(prices[i] / prices[i-1]);
  logReturns.push(logReturn);
}

// Log returns are additive and normally distributed
const totalLogReturn: number = bdpo.sum(logReturns.slice(-30));
const avgDailyLogReturn: number = bdpo.mean(logReturns.slice(-30));
const logReturnVolatility: number = bdpo.std(logReturns.slice(-30));

// Convert back to simple returns
const totalSimpleReturn: number = Math.exp(totalLogReturn) - 1;

// Log transformation for price scaling
const logPrices: number[] = prices.map((price: number) => bdpo.log10(price));
const logPriceRange: number = bdpo.range(logPrices.slice(-50));

bdpo.log(`30-day total return: ${(totalSimpleReturn * 100).toFixed(2)}%`);
bdpo.log(`Daily log return volatility: ${(logReturnVolatility * 100).toFixed(3)}%`);
bdpo.cbrt(x) / bdpo.exp(x) / bdpo.sign(x)
Additional mathematical functions for specialized calculations

Parameters

Function Parameters Description
cbrt(x) Number Cube root of x
exp(x) Number e raised to the power of x
sign(x) Number Sign of x (-1, 0, or 1)

Example Usage

// Volume-price relationship analysis
const volumes: number[] = bdpo.getVolume(data).slice(-30);
const prices: number[] = bdpo.getClose(data).slice(-30);

// Cube root transformation for volume analysis (reduces outlier impact)
const cbrtVolumes: number[] = volumes.map((vol: number) => bdpo.cbrt(vol));
const avgCbrtVolume: number = bdpo.mean(cbrtVolumes);
const currentCbrtVolume: number = bdpo.cbrt(volumes[volumes.length - 1]);

// Exponential decay weighting for recent data
const decayFactor: number = 0.1;
let weightedSum: number = 0;
let totalWeight: number = 0;

for (let i = 0; i < prices.length; i++) {
  const weight: number = bdpo.exp(-decayFactor * (prices.length - 1 - i));
  weightedSum += prices[i] * weight;
  totalWeight += weight;
}
const exponentialMA: number = weightedSum / totalWeight;

// Trend direction analysis using sign function
const priceDiffs: number[] = bdpo.diff(prices);
const trendSigns: number[] = priceDiffs.map((diff: number) => bdpo.sign(diff));
const bullishBars: number = trendSigns.filter((sign: number) => sign > 0).length;
const bearishBars: number = trendSigns.filter((sign: number) => sign < 0).length;
const trendStrength: number = (bullishBars - bearishBars) / trendSigns.length;

bdpo.log(`Volume analysis: Current/Avg = ${(currentCbrtVolume / avgCbrtVolume).toFixed(2)}`);
bdpo.log(`Exponential MA: ${exponentialMA.toFixed(4)}`);
bdpo.log(`Trend strength: ${(trendStrength * 100).toFixed(1)}% (${bullishBars}↑ ${bearishBars}↓)`);
bdpo.sin/cos/tan(x) / bdpo.asin/acos/atan(x) / bdpo.atan2(y, x)
Trigonometric functions for cyclical analysis and technical patterns

Parameters

Function Parameters Description
sin(x) Number (radians) Sine of x
cos(x) Number (radians) Cosine of x
tan(x) Number (radians) Tangent of x
asin(x) Number (-1 to 1) Arcsine of x
acos(x) Number (-1 to 1) Arccosine of x
atan(x) Number Arctangent of x
atan2(y, x) Number, Number Arctangent of y/x (quadrant-aware)

Example Usage

// Cyclical market analysis using trigonometric functions
const prices: number[] = bdpo.getClose(data);
const periods: number = 20; // 20-day cycle analysis

// Generate sine wave oscillator for cycle detection
const cycleValues: number[] = [];
for (let i = 0; i < prices.length; i++) {
  const angle: number = (2 * Math.PI * i) / periods;
  const sineValue: number = bdpo.sin(angle);
  const cosineValue: number = bdpo.cos(angle);
  
  // Combine sine and cosine for quadrature oscillator
  const cycleValue: number = sineValue * 0.7 + cosineValue * 0.3;
  cycleValues.push(cycleValue);
}

// Price momentum direction analysis
const priceReturns: number[] = [];
for (let i = 1; i < prices.length; i++) {
  const returnValue: number = (prices[i] - prices[i-1]) / prices[i-1];
  priceReturns.push(returnValue);
}

// Convert price movements to angular representation
const currentReturn: number = priceReturns[priceReturns.length - 1];
const avgReturn: number = bdpo.mean(priceReturns.slice(-30));

// Use atan2 for quadrant-aware angle calculation
const momentumAngle: number = bdpo.atan2(currentReturn, avgReturn);
const momentumDegrees: number = (momentumAngle * 180) / Math.PI;

// Trend strength using arctangent transformation
const normalizedReturns: number[] = priceReturns.slice(-30).map((ret: number) => {
  return bdpo.atan(ret * 100) * (2 / Math.PI); // Normalize to [-1, 1]
});

const trendStrength: number = bdpo.mean(normalizedReturns);
const trendConsistency: number = 1 - bdpo.std(normalizedReturns);

// Seasonal pattern detection
const dayOfCycle: number = prices.length % periods;
const seasonalPhase: number = (2 * Math.PI * dayOfCycle) / periods;
const expectedCycleValue: number = bdpo.sin(seasonalPhase);

bdpo.log(`Cycle Analysis (${periods}-day):`);
bdpo.log(`Current cycle position: ${(dayOfCycle / periods * 100).toFixed(1)}%`);
bdpo.log(`Expected cycle value: ${expectedCycleValue.toFixed(3)}`);
bdpo.log(`Momentum angle: ${momentumDegrees.toFixed(1)}°`);
bdpo.log(`Trend strength: ${(trendStrength * 100).toFixed(1)}%`);
bdpo.log(`Trend consistency: ${(trendConsistency * 100).toFixed(1)}%`);

Array Operations

bdpo.cumsum(array) / bdpo.diff(array) / bdpo.sort(array)
Array manipulation functions for data analysis and transformations

Parameters

Function Parameters Description
cumsum(array) Array<Number> Cumulative sum array
diff(array) Array<Number> Differences between consecutive elements
sort(array) Array<Number> Sorted copy of array

Example Usage

// Cumulative P&L analysis
const prices: number[] = bdpo.getClose(data);
const returns: number[] = [];

for (let i = 1; i < prices.length; i++) {
  const dailyReturn: number = (prices[i] - prices[i-1]) / prices[i-1];
  returns.push(dailyReturn);
}

// Cumulative returns for equity curve
const cumulativeReturns: number[] = bdpo.cumsum(returns);
const equityCurve: number[] = cumulativeReturns.map((ret: number) => 10000 * (1 + ret));

// Price differences for momentum analysis
const priceDiffs: number[] = bdpo.diff(prices.slice(-30));
const avgPriceChange: number = bdpo.mean(priceDiffs);

// Sorted returns for percentile analysis
const sortedReturns: number[] = bdpo.sort(returns.slice(-100));
const medianReturn: number = bdpo.median(sortedReturns);
const percentile95: number = bdpo.percentile(sortedReturns, 95);
const percentile5: number = bdpo.percentile(sortedReturns, 5);

// Risk metrics
const valueAtRisk5: number = Math.abs(percentile5) * 100; // 5% VaR

bdpo.log(`Current equity: ${equityCurve[equityCurve.length - 1].toFixed(2)}`);
bdpo.log(`Average daily price change: ${avgPriceChange.toFixed(4)}`);
bdpo.log(`5% Value at Risk: ${valueAtRisk5.toFixed(2)}%`);
bdpo.unique(array)
Extract unique values from arrays for pattern analysis

Parameters

Function Parameters Description
unique(array) Array<Number> Array of unique values (removes duplicates)

Example Usage

// Support and resistance level analysis using unique values
const highs: number[] = bdpo.getHigh(data).slice(-100);
const lows: number[] = bdpo.getLow(data).slice(-100);

// Round to meaningful price levels for clustering analysis
const precision: number = 4; // 4 decimal places
const roundedHighs: number[] = highs.map((high: number) => bdpo.round(high, precision));
const roundedLows: number[] = lows.map((low: number) => bdpo.round(low, precision));

// Find unique price levels
const uniqueHighs: number[] = bdpo.unique(roundedHighs);
const uniqueLows: number[] = bdpo.unique(roundedLows);

// Count frequency of each level for significance
const highFrequency: Map = new Map();
const lowFrequency: Map = new Map();

roundedHighs.forEach((high: number) => {
  highFrequency.set(high, (highFrequency.get(high) || 0) + 1);
});

roundedLows.forEach((low: number) => {
  lowFrequency.set(low, (lowFrequency.get(low) || 0) + 1);
});

// Find most significant levels (appeared multiple times)
const significantResistance: number[] = uniqueHighs.filter((level: number) => 
  (highFrequency.get(level) || 0) >= 3
);
const significantSupport: number[] = uniqueLows.filter((level: number) => 
  (lowFrequency.get(level) || 0) >= 3
);

// Current price analysis
const currentPrice: number = bdpo.getClose(data)[data.length - 1];
const nearestResistance: number = significantResistance
  .filter((level: number) => level > currentPrice)
  .reduce((nearest: number, level: number) => 
    Math.abs(level - currentPrice) < Math.abs(nearest - currentPrice) ? level : nearest,
    significantResistance[0]
  );

const nearestSupport: number = significantSupport
  .filter((level: number) => level < currentPrice)
  .reduce((nearest: number, level: number) => 
    Math.abs(level - currentPrice) < Math.abs(nearest - currentPrice) ? level : nearest,
    significantSupport[0]
  );

// Calculate distances and ratios
const resistanceDistance: number = ((nearestResistance - currentPrice) / currentPrice) * 100;
const supportDistance: number = ((currentPrice - nearestSupport) / currentPrice) * 100;

// Unique value statistics
const priceRange: number = bdpo.max(highs) - bdpo.min(lows);
const uniqueHighCount: number = uniqueHighs.length;
const uniqueLowCount: number = uniqueLows.length;
const consolidationRatio: number = 1 - (uniqueHighCount + uniqueLowCount) / (highs.length + lows.length);

bdpo.log(`Price Level Analysis (100 periods):`);
bdpo.log(`Unique high levels: ${uniqueHighCount} / Unique low levels: ${uniqueLowCount}`);
bdpo.log(`Significant resistance levels: ${significantResistance.length}`);
bdpo.log(`Significant support levels: ${significantSupport.length}`);
bdpo.log(`Nearest resistance: ${nearestResistance?.toFixed(precision)} (${resistanceDistance?.toFixed(2)}% away)`);
bdpo.log(`Nearest support: ${nearestSupport?.toFixed(precision)} (${supportDistance?.toFixed(2)}% away)`);
bdpo.log(`Consolidation ratio: ${(consolidationRatio * 100).toFixed(1)}%`);

Utility Functions

bdpo.round(x, n) / bdpo.floor(x) / bdpo.ceil(x) / bdpo.clamp(x, min, max) / bdpo.fix(x)
Utility functions for number formatting, value constraints, and data processing

Parameters

Function Parameters Description
round(x, n) Number, Number (optional) Round to n decimal places
floor(x) Number Round down to integer
ceil(x) Number Round up to integer
clamp(x, min, max) Number, Number, Number Constrain value between min and max
fix(x) Number Remove fractional part (truncate towards zero)

Example Usage

// Position sizing with constraints
const accountBalance: number = bdpo.getBalance();
const riskPercent: number = 2.5;
const currentPrice: number = bdpo.getAsk();

// Calculate raw position size
const riskAmount: number = accountBalance * (riskPercent / 100);
const rawPositionSize: number = riskAmount / currentPrice;

// Apply broker constraints
const minLotSize: number = bdpo.getMinQty() || 0.01;
const maxLotSize: number = bdpo.getMaxQty() || 100;
const stepSize: number = bdpo.getStepSize() || 0.01;

// Round to valid lot size
const roundedSize: number = bdpo.floor(rawPositionSize / stepSize) * stepSize;
const constrainedSize: number = bdpo.clamp(roundedSize, minLotSize, maxLotSize);

// Price formatting for orders
const stopLoss: number = currentPrice * 0.98;
const takeProfit: number = currentPrice * 1.04;
const precision: number = bdpo.getPrecision() || 5;

const formattedStopLoss: number = bdpo.round(stopLoss, precision);
const formattedTakeProfit: number = bdpo.round(takeProfit, precision);

// Risk calculation
const actualRisk: number = constrainedSize * currentPrice * 0.02; // 2% price move
const riskPercentage: number = (actualRisk / accountBalance) * 100;

bdpo.log(`Position size: ${constrainedSize} lots`);
bdpo.log(`Stop Loss: ${formattedStopLoss}`);
bdpo.log(`Take Profit: ${formattedTakeProfit}`);
bdpo.log(`Actual risk: ${riskPercentage.toFixed(2)}%`);
bdpo.percentile(array, p) / bdpo.quantileSeq(array, q)
Statistical percentile and quantile calculations for risk analysis

Parameters

Function Parameters Description
percentile(array, p) Array<Number>, Number (0-100) Percentile value (0-100 scale)
quantileSeq(array, q) Array<Number>, Number (0-1) Quantile value (0-1 scale)

Example Usage

// Risk analysis using percentiles
const prices: number[] = bdpo.getClose(data);
const returns: number[] = [];

for (let i = 1; i < prices.length; i++) {
  const dailyReturn: number = (prices[i] - prices[i-1]) / prices[i-1];
  returns.push(dailyReturn);
}

// Value at Risk (VaR) calculations
const var95: number = bdpo.percentile(returns.slice(-252), 5); // 5th percentile = 95% VaR
const var99: number = bdpo.percentile(returns.slice(-252), 1); // 1st percentile = 99% VaR

// Expected Shortfall (average of worst 5% outcomes)
const sortedReturns: number[] = bdpo.sort(returns.slice(-252));
const worst5Percent: number[] = sortedReturns.slice(0, Math.floor(sortedReturns.length * 0.05));
const expectedShortfall: number = bdpo.mean(worst5Percent);

// Performance percentiles
const currentReturn: number = returns[returns.length - 1];
const returnRank: number = returns.filter(r => r <= currentReturn).length / returns.length;
const percentileRank: number = returnRank * 100;

// Quartile analysis
const q1: number = bdpo.quantileSeq(returns.slice(-100), 0.25);
const q2: number = bdpo.quantileSeq(returns.slice(-100), 0.50); // median
const q3: number = bdpo.quantileSeq(returns.slice(-100), 0.75);
const iqr: number = q3 - q1; // Interquartile range

bdpo.log(`95% VaR: ${(Math.abs(var95) * 100).toFixed(2)}%`);
bdpo.log(`99% VaR: ${(Math.abs(var99) * 100).toFixed(2)}%`);
bdpo.log(`Expected Shortfall: ${(Math.abs(expectedShortfall) * 100).toFixed(2)}%`);
bdpo.log(`Current return percentile: ${percentileRank.toFixed(1)}%`);

Random Functions

bdpo.random() / bdpo.randomInt(min, max)
Random number generation for Monte Carlo simulations and testing

Parameters

Function Parameters Description
random() None Random number between 0 and 1
randomInt(min, max) Number, Number Random integer between min and max

Example Usage

// Monte Carlo simulation for risk assessment
function runMonteCarloSimulation(initialPrice: number, days: number, numSimulations: number): number[] {
  const finalPrices: number[] = [];
  const avgReturn: number = 0.0008; // 0.08% daily return
  const volatility: number = 0.02; // 2% daily volatility
  
  for (let sim = 0; sim < numSimulations; sim++) {
    let price: number = initialPrice;
    
    for (let day = 0; day < days; day++) {
      // Generate random return using Box-Muller transformation
      const u1: number = bdpo.random();
      const u2: number = bdpo.random();
      const z: number = bdpo.sqrt(-2 * bdpo.log(u1)) * Math.cos(2 * Math.PI * u2);
      
      const dailyReturn: number = avgReturn + volatility * z;
      price = price * (1 + dailyReturn);
    }
    
    finalPrices.push(price);
  }
  
  return finalPrices;
}

// Run simulation
const currentPrice: number = bdpo.getClose(data)[data.length - 1];
const simulatedPrices: number[] = runMonteCarloSimulation(currentPrice, 30, 1000);

// Analyze results
const avgFinalPrice: number = bdpo.mean(simulatedPrices);
const priceVolatility: number = bdpo.std(simulatedPrices);
const priceRange: number = bdpo.range(simulatedPrices);

// Risk metrics from simulation
const var95Simulation: number = bdpo.percentile(simulatedPrices, 5);
const expectedPrice: number = avgFinalPrice;
const worstCase: number = bdpo.min(simulatedPrices);
const bestCase: number = bdpo.max(simulatedPrices);

// Random portfolio rebalancing
const assets: string[] = ['BTC', 'ETH', 'SOL', 'ADA'];
const randomAsset: string = assets[bdpo.randomInt(0, assets.length - 1)];
const randomWeight: number = bdpo.random() * 0.3 + 0.1; // 10-40% allocation

bdpo.log(`Monte Carlo Results (30 days, 1000 simulations):`);
bdpo.log(`Expected price: ${expectedPrice.toFixed(2)}`);
bdpo.log(`95% confidence range: ${var95Simulation.toFixed(2)} - ${bdpo.percentile(simulatedPrices, 95).toFixed(2)}`);
bdpo.log(`Random rebalance: ${randomWeight.toFixed(1)}% to ${randomAsset}`);

Advanced Operations

bdpo.dot(a, b) / bdpo.cross(a, b) / bdpo.norm(array)
Vector operations for advanced mathematical analysis

Parameters

Function Parameters Description
dot(a, b) Array, Array Dot product of two vectors
cross(a, b) Array, Array Cross product of two vectors
norm(array) Array<Number> Euclidean norm (length) of vector

Example Usage

// Correlation analysis using dot product
const prices1: number[] = bdpo.getClose(data).slice(-30);
const prices2: number[] = bdpo.getClose(referenceData).slice(-30);

// Normalize price series (subtract mean, divide by std)
const mean1: number = bdpo.mean(prices1);
const mean2: number = bdpo.mean(prices2);
const std1: number = bdpo.std(prices1);
const std2: number = bdpo.std(prices2);

const normalized1: number[] = prices1.map(p => (p - mean1) / std1);
const normalized2: number[] = prices2.map(p => (p - mean2) / std2);

// Calculate correlation using dot product
const correlation: number = bdpo.dot(normalized1, normalized2) / normalized1.length;

// Vector magnitude analysis
const returns1: number[] = bdpo.diff(prices1);
const returns2: number[] = bdpo.diff(prices2);

const magnitude1: number = bdpo.norm(returns1);
const magnitude2: number = bdpo.norm(returns2);

// Relative strength comparison
const strengthRatio: number = magnitude1 / magnitude2;

// Portfolio diversification analysis
const weights: number[] = [0.6, 0.4]; // 60% asset1, 40% asset2
const returns: number[] = [normalized1[normalized1.length - 1], normalized2[normalized2.length - 1]];

const portfolioReturn: number = bdpo.dot(weights, returns);
const portfolioRisk: number = bdpo.norm([
  weights[0] * returns[0],
  weights[1] * returns[1]
]);

bdpo.log(`Correlation: ${correlation.toFixed(3)}`);
bdpo.log(`Relative strength: ${strengthRatio.toFixed(2)}`);
bdpo.log(`Portfolio return: ${(portfolioReturn * 100).toFixed(2)}%`);
bdpo.log(`Portfolio risk: ${(portfolioRisk * 100).toFixed(2)}%`);
Pro Tips:
  • Use logarithmic returns for better statistical properties and easier aggregation
  • Apply percentile analysis for robust risk metrics that handle outliers well
  • Combine multiple mathematical functions for complex financial calculations
  • Use Monte Carlo simulations to test strategy robustness under various scenarios
  • Leverage vector operations for portfolio analysis and correlation studies
  • Always validate mathematical operations with reasonable bounds checking

Console Functions

Debugging, logging, and output management tools for strategy development

Quick Reference: Console functions provide essential debugging and logging capabilities for strategy development. These functions allow you to output messages, track execution flow, and monitor variable values during backtesting and live trading. All console output is displayed in the BDPO console interface.

Basic Logging

bdpo.log(message, level?)
Advanced logging with level support and automatic timestamping

Parameters

Parameter Type Description
message String | Number | Object The message or data to log
level String (optional) Log level: "info", "warn", "error", "success", "debug". Default: "info"

Returns

void - Sends timestamped message with level to console

Example Usage

// Basic logging (info level by default)
bdpo.log("Strategy started successfully");

// Explicit log levels
bdpo.log("Market conditions analyzed", "info");
bdpo.log("High volatility detected", "warn");
bdpo.log("Order execution failed", "error");
bdpo.log("Position opened successfully", "success");
bdpo.log("Debug: RSI calculation complete", "debug");

// Conditional logging based on strategy state
const rsi: number[] = bdpo.rsi(bdpo.getClose(data), 14);
const currentRSI: number = rsi[rsi.length - 1];

if (currentRSI > 70) {
  bdpo.log(`RSI overbought: ${currentRSI.toFixed(2)}`, "warn");
} else if (currentRSI < 30) {
  bdpo.log(`RSI oversold: ${currentRSI.toFixed(2)}`, "warn");
} else {
  bdpo.log(`RSI normal: ${currentRSI.toFixed(2)}`, "info");
}

// Log complex trading decisions
const signal = analyzeMarketConditions();
if (signal.action === "buy") {
  bdpo.log(`BUY Signal: Confidence ${signal.confidence}%, Entry ${signal.price}`, "success");
} else if (signal.action === "sell") {
  bdpo.log(`SELL Signal: Confidence ${signal.confidence}%, Entry ${signal.price}`, "error");
}

Advanced Logging Functions

bdpo.logInfo(message)
Log informational messages with automatic info-level formatting

Parameters

Parameter Type Description
message String | Number | Object The informational message to log

Returns

void - Logs message with "info" level and timestamp

Example Usage

// Strategy initialization
bdpo.logInfo("Strategy initialization complete");
bdpo.logInfo(`Trading ${bdpo.getSymbol()} on ${bdpo.getExchange()}`);

// Market analysis updates
const currentPrice: number = bdpo.getClose(data)[data.length - 1];
const sma20: number[] = bdpo.sma(bdpo.getClose(data), 20);
bdpo.logInfo(`Current price: ${currentPrice.toFixed(4)}, SMA20: ${sma20[sma20.length - 1].toFixed(4)}`);

// Position updates
const openPositions: any[] = bdpo.getOpenPositions();
bdpo.logInfo(`Active positions: ${openPositions.length}`);

// Performance reporting
const balance: number = bdpo.getBalance();
const equity: number = bdpo.getEquity();
const profit: number = bdpo.getProfit();
bdpo.logInfo(`Balance: ${balance.toFixed(2)}, Equity: ${equity.toFixed(2)}, P&L: ${profit.toFixed(2)}`);
bdpo.logWarn(message)
Log warning messages for potentially problematic conditions

Parameters

Parameter Type Description
message String | Number | Object The warning message to log

Returns

void - Logs message with "warn" level and timestamp

Example Usage

// Risk management warnings
const balance: number = bdpo.getBalance();
const equity: number = bdpo.getEquity();
const drawdown: number = ((balance - equity) / balance) * 100;

if (drawdown > 10) {
  bdpo.logWarn(`High drawdown detected: ${drawdown.toFixed(2)}%`);
}

// Market condition warnings
const spread: number = bdpo.getSpread();
if (spread > 3.0) {
  bdpo.logWarn(`Wide spread detected: ${spread.toFixed(1)} pips`);
}

// Volatility warnings
const atr: number[] = bdpo.atr(bdpo.getHigh(data), bdpo.getLow(data), bdpo.getClose(data), 14);
const currentATR: number = atr[atr.length - 1];
const avgATR: number = bdpo.mean(atr.slice(-50));

if (currentATR > avgATR * 2) {
  bdpo.logWarn(`High volatility: ATR ${currentATR.toFixed(4)} vs avg ${avgATR.toFixed(4)}`);
}

// Position size warnings
const openPositions: any[] = bdpo.getOpenPositions();
if (openPositions.length > 5) {
  bdpo.logWarn(`High position count: ${openPositions.length} open trades`);
}
bdpo.logError(message)
Log error messages for critical issues and failures

Parameters

Parameter Type Description
message String | Number | Object The error message to log

Returns

void - Logs message with "error" level and timestamp

Example Usage

// Order execution errors
try {
  const result = bdpo.orderSend({
    orderType: "BUY",
    lotSize: 1.0,
    stopLoss: 1.2500,
    takeProfit: 1.2800
  });
  
  if (!result.success) {
    bdpo.logError(`Order failed: ${result.message}`);
  }
} catch (error) {
  bdpo.logError(`Order execution exception: ${error.message}`);
}

// Data validation errors
const chartData: any[] = bdpo.getChartData(data);
if (!chartData || chartData.length === 0) {
  bdpo.logError("No chart data available - cannot proceed with analysis");
  return;
}

// Calculation errors
try {
  const rsi: number[] = bdpo.rsi(bdpo.getClose(data), 14);
  if (rsi.length === 0) {
    bdpo.logError("RSI calculation failed - insufficient data");
  }
} catch (error) {
  bdpo.logError(`Technical indicator error: ${error.message}`);
}

// Account status errors
const balance: number = bdpo.getBalance();
if (balance <= 0) {
  bdpo.logError("Account balance is zero or negative - trading halted");
}
bdpo.logSuccess(message)
Log success messages for completed operations and achievements

Parameters

Parameter Type Description
message String | Number | Object The success message to log

Returns

void - Logs message with "success" level and timestamp

Example Usage

// Successful trade execution
const orderResult = bdpo.orderSend({
  orderType: "BUY",
  lotSize: 1.0,
  stopLoss: 1.2500,
  takeProfit: 1.2800
});

if (orderResult.success) {
  bdpo.logSuccess(`Order placed successfully: ${orderResult.orderId}`);
  bdpo.logSuccess(`Entry: ${orderResult.price}, SL: ${orderResult.stopLoss}, TP: ${orderResult.takeProfit}`);
}

// Profit targets achieved
const closedPositions: any[] = bdpo.getClosedPositions();
const recentTrades = closedPositions.slice(-10);
const winningTrades = recentTrades.filter(trade => trade.profit > 0);

if (winningTrades.length >= 7) {
  bdpo.logSuccess(`Strong performance: ${winningTrades.length}/10 winning trades`);
}

// Milestone achievements
const totalProfit: number = bdpo.getProfit();
if (totalProfit > 1000) {
  bdpo.logSuccess(`Milestone achieved: Total profit exceeds $1,000 (${totalProfit.toFixed(2)})`);
}

// Risk management success
const maxDrawdown: number = calculateMaxDrawdown();
if (maxDrawdown < 5) {
  bdpo.logSuccess(`Excellent risk control: Max drawdown only ${maxDrawdown.toFixed(2)}%`);
}
bdpo.logDebug(message)
Log detailed debugging information for development and troubleshooting

Parameters

Parameter Type Description
message String | Number | Object The debug message to log

Returns

void - Logs message with "debug" level and timestamp

Example Usage

// Variable state debugging
const prices: number[] = bdpo.getClose(data);
const volumes: number[] = bdpo.getVolume(data);

bdpo.logDebug(`Data length: ${prices.length} candles`);
bdpo.logDebug(`Latest price: ${prices[prices.length - 1]}`);
bdpo.logDebug(`Latest volume: ${volumes[volumes.length - 1]}`);

// Calculation step debugging
const sma20: number[] = bdpo.sma(prices, 20);
const ema12: number[] = bdpo.ema(prices, 12);
const rsi: number[] = bdpo.rsi(prices, 14);

bdpo.logDebug(`SMA20: ${sma20[sma20.length - 1].toFixed(4)}`);
bdpo.logDebug(`EMA12: ${ema12[ema12.length - 1].toFixed(4)}`);
bdpo.logDebug(`RSI14: ${rsi[rsi.length - 1].toFixed(2)}`);

// Signal generation debugging
const signalStrength: number = calculateSignalStrength();
const marketCondition: string = analyzeMarketCondition();

bdpo.logDebug(`Signal strength: ${signalStrength.toFixed(3)}`);
bdpo.logDebug(`Market condition: ${marketCondition}`);

// Performance debugging
const executionStart: number = performance.now();
const complexCalculation = performComplexAnalysis();
const executionTime: number = performance.now() - executionStart;

bdpo.logDebug(`Complex analysis completed in ${executionTime.toFixed(2)}ms`);
bdpo.logDebug(`Analysis result: ${JSON.stringify(complexCalculation)}`);

Specialized Logging Functions

bdpo.logInfo/logWarn/logError/logSuccess/logDebug(message)
Convenience functions for specific log levels with pre-defined styling

Functions

Function Level Use Case
logInfo(message) info General information, status updates
logWarn(message) warn Warnings, unusual conditions, risk alerts
logError(message) error Errors, failures, critical issues
logSuccess(message) success Successful operations, achievements
logDebug(message) debug Detailed debugging information

Example Usage

// Strategy lifecycle logging
class TradingStrategy {
  public riskPercent: number = 2;
  public stopLossPercent: number = 3;
  
  private _positions: any[] = [];
  private _initialized: boolean = false;

  __init__(): void {
    bdpo.logInfo("Initializing trading strategy...");
    
    try {
      this._positions = [];
      this._initialized = true;
      bdpo.logSuccess("Strategy initialization completed successfully");
      bdpo.logInfo(`Risk per trade: ${this.riskPercent}%, Stop loss: ${this.stopLossPercent}%`);
    } catch (error) {
      bdpo.logError(`Strategy initialization failed: ${error.message}`);
      this._initialized = false;
    }
  }

  __tick__(): any {
    if (!this._initialized) {
      bdpo.logError("Strategy not initialized - skipping tick");
      return null;
    }

    bdpo.logDebug("Processing new market tick...");
    
    // Market analysis
    const prices: number[] = bdpo.getClose(data);
    const currentPrice: number = prices[prices.length - 1];
    const sma20: number[] = bdpo.sma(prices, 20);
    const rsi: number[] = bdpo.rsi(prices, 14);
    
    bdpo.logDebug(`Price: ${currentPrice}, SMA20: ${sma20[sma20.length - 1].toFixed(4)}, RSI: ${rsi[rsi.length - 1].toFixed(2)}`);
    
    // Risk management checks
    const accountBalance: number = bdpo.getBalance();
    const openPositions: any[] = bdpo.getOpenPositions();
    
    if (openPositions.length >= 5) {
      bdpo.logWarn(`Maximum positions reached: ${openPositions.length}/5`);
      return null;
    }
    
    if (accountBalance < 1000) {
      bdpo.logWarn(`Low account balance: $${accountBalance.toFixed(2)}`);
    }
    
    // Signal generation
    const signal = this.generateSignal(currentPrice, sma20, rsi);
    
    if (signal.type === "BUY") {
      bdpo.logInfo(`BUY signal detected: ${signal.reason}`);
      this.executeBuyOrder(signal);
    } else if (signal.type === "SELL") {
      bdpo.logInfo(`SELL signal detected: ${signal.reason}`);
      this.executeSellOrder(signal);
    } else {
      bdpo.logDebug("No trading signal - holding position");
    }
    
    return {
      currentPrice,
      signal: signal.type,
      openPositions: openPositions.length
    };
  }

  private executeBuyOrder(signal: any): void {
    try {
      const orderResult = bdpo.orderSend({
        symbol: bdpo.getSymbol(),
        cmd: 0, // Buy
        volume: this.calculatePositionSize(),
        price: signal.price,
        slippage: 3,
        stoploss: signal.stopLoss,
        takeprofit: signal.takeProfit
      });
      
      if (orderResult && orderResult.ticket) {
        bdpo.logSuccess(`BUY order executed: Ticket #${orderResult.ticket}, Price: ${signal.price}`);
        this._positions.push(orderResult);
      } else {
        bdpo.logError("BUY order execution failed - no ticket returned");
      }
    } catch (error) {
      bdpo.logError(`BUY order execution error: ${error.message}`);
    }
  }

  private executeSellOrder(signal: any): void {
    try {
      const openLongs = this._positions.filter(pos => pos.cmd === 0 && pos.close_time === 0);
      
      if (openLongs.length === 0) {
        bdpo.logWarn("SELL signal but no open long positions to close");
        return;
      }
      
      openLongs.forEach(position => {
        const closeResult = bdpo.orderClose(position.ticket, position.volume, signal.price, 3);
        if (closeResult) {
          bdpo.logSuccess(`Position closed: Ticket #${position.ticket}, Exit: ${signal.price}`);
        } else {
          bdpo.logError(`Failed to close position: Ticket #${position.ticket}`);
        }
      });
    } catch (error) {
      bdpo.logError(`SELL order execution error: ${error.message}`);
    }
  }

  __shutdown__(): void {
    bdpo.logInfo("Shutting down trading strategy...");
    
    try {
      // Close all open positions
      const openPositions = this._positions.filter(pos => pos.close_time === 0);
      if (openPositions.length > 0) {
        bdpo.logWarn(`Closing ${openPositions.length} open positions during shutdown`);
        openPositions.forEach(pos => {
          bdpo.orderClose(pos.ticket, pos.volume, bdpo.getAsk(), 5);
        });
      }
      
      bdpo.logSuccess("Strategy shutdown completed");
      bdpo.logInfo(`Final statistics: ${this._positions.length} total trades executed`);
    } catch (error) {
      bdpo.logError(`Strategy shutdown error: ${error.message}`);
    }
  }
}

Common Debugging Patterns

Debugging Best Practices
Common patterns and techniques for effective strategy debugging

Debugging Techniques

Variable Tracking

Monitor variable values and state changes throughout execution

Execution Flow

Trace program execution and decision points

Error Handling

Comprehensive error logging and recovery strategies

Performance Monitoring

Track execution times and resource usage

Example Usage

// Advanced debugging patterns for strategy development

class AdvancedTradingStrategy {
  public debugMode: boolean = true;
  public logLevel: string = "debug";
  
  private _executionTimes: number[] = [];
  private _decisionLog: any[] = [];

  __tick__(): any {
    const startTime: number = performance.now();
    
    if (this.debugMode) {
      bdpo.logDebug("=== TICK START ===");
    }
    
    try {
      // 1. Variable state tracking
      const marketData = this.getMarketData();
      this.logVariableState("Market Data", marketData);
      
      // 2. Decision flow tracking
      const decision = this.makeDecision(marketData);
      this.logDecisionFlow(decision);
      
      // 3. Performance monitoring
      const endTime: number = performance.now();
      const executionTime: number = endTime - startTime;
      this._executionTimes.push(executionTime);
      
      if (this.debugMode) {
        bdpo.logDebug(`Execution time: ${executionTime.toFixed(2)}ms`);
        bdpo.logDebug("=== TICK END ===");
      }
      
      // 4. Periodic performance reports
      if (this._executionTimes.length % 100 === 0) {
        this.logPerformanceReport();
      }
      
      return decision;
      
    } catch (error) {
      // 5. Comprehensive error logging
      this.logError("Tick execution error", error, {
        marketData: this.getMarketData(),
        executionTime: performance.now() - startTime,
        tickCount: this._executionTimes.length
      });
      return null;
    }
  }

  private getMarketData(): any {
    const prices: number[] = bdpo.getClose(data);
    const volumes: number[] = bdpo.getVolume(data);
    
    return {
      currentPrice: prices[prices.length - 1],
      previousPrice: prices[prices.length - 2],
      currentVolume: volumes[volumes.length - 1],
      avgVolume: bdpo.mean(volumes.slice(-20)),
      timestamp: Date.now()
    };
  }

  private logVariableState(label: string, variables: any): void {
    if (!this.debugMode) return;
    
    bdpo.logDebug(`${label}:`);
    Object.entries(variables).forEach(([key, value]) => {
      bdpo.logDebug(`  ${key}: ${typeof value === 'number' ? value.toFixed(4) : value}`);
    });
  }

  private makeDecision(marketData: any): any {
    const decision = {
      action: "HOLD",
      confidence: 0,
      reasons: [],
      timestamp: Date.now()
    };
    
    // Example decision logic with detailed logging
    const priceChange = ((marketData.currentPrice - marketData.previousPrice) / marketData.previousPrice) * 100;
    const volumeRatio = marketData.currentVolume / marketData.avgVolume;
    
    bdpo.logDebug(`Price change: ${priceChange.toFixed(2)}%`);
    bdpo.logDebug(`Volume ratio: ${volumeRatio.toFixed(2)}`);
    
    if (priceChange > 1 && volumeRatio > 1.5) {
      decision.action = "BUY";
      decision.confidence = 75;
      decision.reasons.push("Strong upward momentum with high volume");
      bdpo.logInfo(`BUY decision: Price +${priceChange.toFixed(2)}%, Volume ${volumeRatio.toFixed(2)}x`);
    } else if (priceChange < -1 && volumeRatio > 1.5) {
      decision.action = "SELL";
      decision.confidence = 70;
      decision.reasons.push("Strong downward momentum with high volume");
      bdpo.logInfo(`SELL decision: Price ${priceChange.toFixed(2)}%, Volume ${volumeRatio.toFixed(2)}x`);
    } else {
      bdpo.logDebug(`HOLD decision: Insufficient momentum (Price: ${priceChange.toFixed(2)}%, Volume: ${volumeRatio.toFixed(2)}x)`);
    }
    
    return decision;
  }

  private logDecisionFlow(decision: any): void {
    this._decisionLog.push(decision);
    
    if (this.debugMode) {
      bdpo.logDebug(`Decision: ${decision.action} (${decision.confidence}% confidence)`);
      decision.reasons.forEach((reason: string) => {
        bdpo.logDebug(`  Reason: ${reason}`);
      });
    }
    
    // Log decision summary every 50 decisions
    if (this._decisionLog.length % 50 === 0) {
      this.logDecisionSummary();
    }
  }

  private logPerformanceReport(): void {
    const avgTime = bdpo.mean(this._executionTimes);
    const maxTime = bdpo.max(this._executionTimes);
    const minTime = bdpo.min(this._executionTimes);
    
    bdpo.logInfo("=== PERFORMANCE REPORT ===");
    bdpo.logInfo(`Avg execution time: ${avgTime.toFixed(2)}ms`);
    bdpo.logInfo(`Max execution time: ${maxTime.toFixed(2)}ms`);
    bdpo.logInfo(`Min execution time: ${minTime.toFixed(2)}ms`);
    bdpo.logInfo(`Total ticks processed: ${this._executionTimes.length}`);
    
    if (avgTime > 10) {
      bdpo.logWarn("High average execution time detected - consider optimization");
    }
  }

  private logDecisionSummary(): void {
    const recentDecisions = this._decisionLog.slice(-50);
    const buyCount = recentDecisions.filter(d => d.action === "BUY").length;
    const sellCount = recentDecisions.filter(d => d.action === "SELL").length;
    const holdCount = recentDecisions.filter(d => d.action === "HOLD").length;
    
    bdpo.logInfo("=== DECISION SUMMARY (Last 50) ===");
    bdpo.logInfo(`BUY: ${buyCount}, SELL: ${sellCount}, HOLD: ${holdCount}`);
    
    const avgConfidence = bdpo.mean(recentDecisions.map(d => d.confidence));
    bdpo.logInfo(`Average confidence: ${avgConfidence.toFixed(1)}%`);
  }

  private logError(message: string, error: any, context: any): void {
    bdpo.logError(`${message}: ${error.message}`);
    bdpo.logError(`Stack trace: ${error.stack}`);
    
    // Log relevant context
    bdpo.logError("Error context:");
    Object.entries(context).forEach(([key, value]) => {
      bdpo.logError(`  ${key}: ${JSON.stringify(value)}`);
    });
  }
}
Pro Tips:
  • Use different log levels strategically - debug for development, info for production monitoring
  • Include context information like timestamps, prices, and decision factors in your logs
  • Implement conditional logging based on market conditions or strategy state
  • Log both successful operations and errors to maintain complete execution history
  • Use structured logging with consistent formatting for easier analysis
  • Consider performance impact - avoid excessive logging in production strategies
  • Log entry and exit points of major functions to trace execution flow
  • Include variable values and calculations in debug logs for troubleshooting

Strategy Examples

Complete trading strategy implementations using BDPO framework

Learn by Example: These examples demonstrate real-world trading strategies implemented using the BDPO class-based structure. Each strategy includes entry/exit logic, risk management, and performance tracking.

Trend Following Strategies

Moving Average Crossover Strategy
Classic dual moving average system with risk management

Strategy Overview

This strategy uses two moving averages of different periods to identify trend changes. When the faster MA crosses above the slower MA, it generates a buy signal. When the faster MA crosses below the slower MA, it generates a sell signal.

class MovingAverageCrossoverStrategy {
  // Public configuration parameters
  public fastPeriod: number = 12;
  public slowPeriod: number = 26;
  public riskPercentPerTrade: number = 2; // 2% risk per trade
  public stopLossPercent: number = 3;     // 3% stop loss
  public takeProfitPercent: number = 9;   // 3:1 risk/reward ratio
  public maxPositions: number = 1;        // Maximum concurrent positions
  public enableTrailingStop: boolean = true;
  public trailingStopPercent: number = 1.5;

  // Private state variables
  private _positions: any[] = [];
  private _fastMA: number[] = [];
  private _slowMA: number[] = [];
  private _lastCrossover: string | null = null;
  private _tradeCount: number = 0;
  private _winCount: number = 0;

  __init__(): void {
    // Initialize strategy state
    this._positions = [];
    this._lastCrossover = null;
    this._tradeCount = 0;
    this._winCount = 0;
    
    bdpo.logInfo(`MA Crossover Strategy initialized:`);
    bdpo.logInfo(`Fast Period: ${this.fastPeriod}, Slow Period: ${this.slowPeriod}`);
    bdpo.logInfo(`Risk per trade: ${this.riskPercentPerTrade}%, Stop Loss: ${this.stopLossPercent}%`);
  }

  __tick__(): any {
    const prices: number[] = bdpo.getClosePrices();
    const currentPrice: number = prices[prices.length - 1];
    
    if (prices.length < this.slowPeriod + 1) {
      return { status: "warming_up", required: this.slowPeriod + 1, current: prices.length };
    }

    // Calculate moving averages
    this._fastMA = bdpo.ema(prices, this.fastPeriod);
    this._slowMA = bdpo.ema(prices, this.slowPeriod);

    const currentFast: number = this._fastMA[this._fastMA.length - 1];
    const currentSlow: number = this._slowMA[this._slowMA.length - 1];
    const prevFast: number = this._fastMA[this._fastMA.length - 2];
    const prevSlow: number = this._slowMA[this._slowMA.length - 2];

    // Check for crossover signals
    const openPositions = this.getOpenPositions();
    let signal: any = null;

    // Bullish crossover (fast MA crosses above slow MA)
    if (prevFast <= prevSlow && currentFast > currentSlow && openPositions.length === 0) {
      signal = this.generateBuySignal(currentPrice);
      this._lastCrossover = 'bullish';
    }
    // Bearish crossover (fast MA crosses below slow MA) 
    else if (prevFast >= prevSlow && currentFast < currentSlow && openPositions.length > 0) {
      signal = this.generateSellSignal(currentPrice);
      this._lastCrossover = 'bearish';
    }

    // Update trailing stops
    if (this.enableTrailingStop) {
      this.updateTrailingStops(currentPrice);
    }

    // Plot indicators
    bdpo.plot("splines", [this._fastMA], { splineColor: "#4CAF50", splineWidth: 2, skipNan: true }, { title: `Fast EMA ${this.fastPeriod}` });
    bdpo.plot("splines", [this._slowMA], { splineColor: "#F44336", splineWidth: 2, skipNan: true }, { title: `Slow EMA ${this.slowPeriod}` });

    return {
      signal: signal,
      indicators: {
        fastMA: currentFast,
        slowMA: currentSlow,
        spread: ((currentFast - currentSlow) / currentSlow * 100).toFixed(2)
      },
      positions: openPositions.length,
      lastCrossover: this._lastCrossover,
      performance: {
        totalTrades: this._tradeCount,
        winRate: this._tradeCount > 0 ? (this._winCount / this._tradeCount * 100).toFixed(1) : 0
      }
    };
  }

  private generateBuySignal(price: number): any {
    const positionSize = this.calculatePositionSize(price);
    const stopLoss = price * (1 - this.stopLossPercent / 100);
    const takeProfit = price * (1 + this.takeProfitPercent / 100);

    try {
      const order = bdpo.orderSend({
        orderType: "BUY",
        lotSize: positionSize,
        stopLoss: stopLoss,
        takeProfit: takeProfit,
        note: "MA Crossover Bullish Entry"
      });

      if (order) {
        this._positions.push({
          ...order,
          entryPrice: price,
          initialStopLoss: stopLoss,
          trailingStop: this.enableTrailingStop ? stopLoss : null,
          entryTime: Date.now()
        });

        bdpo.logSuccess(`BUY order placed: Size=${positionSize}, Entry=${price.toFixed(4)}, SL=${stopLoss.toFixed(4)}, TP=${takeProfit.toFixed(4)}`);
        
        return {
          type: "BUY",
          price: price,
          size: positionSize,
          stopLoss: stopLoss,
          takeProfit: takeProfit
        };
      }
    } catch (error) {
      bdpo.logError(`Failed to place BUY order: ${error.message}`);
    }

    return null;
  }

  private generateSellSignal(price: number): any {
    const openPositions = this.getOpenPositions();
    
    openPositions.forEach(position => {
      try {
        const closeOrder = bdpo.orderClose(position.ticket, position.volume, price, 3);
        
        if (closeOrder) {
          const profit = (price - position.entryPrice) * position.volume;
          this._tradeCount++;
          
          if (profit > 0) {
            this._winCount++;
            bdpo.logSuccess(`Position closed with profit: +$${profit.toFixed(2)}`);
          } else {
            bdpo.logWarn(`Position closed with loss: -$${Math.abs(profit).toFixed(2)}`);
          }

          // Remove from positions array
          this._positions = this._positions.filter(p => p.ticket !== position.ticket);
        }
      } catch (error) {
        bdpo.logError(`Failed to close position: ${error.message}`);
      }
    });

    return {
      type: "SELL",
      price: price,
      closedPositions: openPositions.length
    };
  }

  private calculatePositionSize(price: number): number {
    const accountBalance = bdpo.getBalance();
    const riskAmount = accountBalance * (this.riskPercentPerTrade / 100);
    const stopLossDistance = price * (this.stopLossPercent / 100);
    
    const positionSize = riskAmount / stopLossDistance;
    
    // Apply position size limits
    const minSize = bdpo.getMinQty() || 0.01;
    const maxSize = bdpo.getMaxQty() || 10;
    
    return Math.max(minSize, Math.min(maxSize, positionSize));
  }

  private updateTrailingStops(currentPrice: number): void {
    const openPositions = this.getOpenPositions();
    
    openPositions.forEach(position => {
      if (position.trailingStop) {
        const trailingDistance = currentPrice * (this.trailingStopPercent / 100);
        const newTrailingStop = currentPrice - trailingDistance;
        
        // Only update if new trailing stop is higher than current
        if (newTrailingStop > position.trailingStop) {
          position.trailingStop = newTrailingStop;
          
          // Update the actual stop loss order
          try {
            bdpo.orderModify(position.ticket, position.openPrice, newTrailingStop, position.takeProfit);
            bdpo.logDebug(`Trailing stop updated to ${newTrailingStop.toFixed(4)} for position ${position.ticket}`);
          } catch (error) {
            bdpo.logError(`Failed to update trailing stop: ${error.message}`);
          }
        }
      }
    });
  }

  private getOpenPositions(): any[] {
    return this._positions.filter(pos => pos.closeTime === 0 || pos.closeTime === undefined);
  }

  __shutdown__(): void {
    // Close all open positions
    const openPositions = this.getOpenPositions();
    
    if (openPositions.length > 0) {
      bdpo.logInfo(`Closing ${openPositions.length} open positions during shutdown`);
      
      openPositions.forEach(position => {
        try {
          bdpo.orderClose(position.ticket, position.volume, bdpo.getAsk(), 5);
        } catch (error) {
          bdpo.logError(`Failed to close position during shutdown: ${error.message}`);
        }
      });
    }

    // Log final performance statistics
    const winRate = this._tradeCount > 0 ? (this._winCount / this._tradeCount * 100) : 0;
    bdpo.logInfo("=== STRATEGY PERFORMANCE SUMMARY ===");
    bdpo.logInfo(`Total Trades: ${this._tradeCount}`);
    bdpo.logInfo(`Winning Trades: ${this._winCount}`);
    bdpo.logInfo(`Win Rate: ${winRate.toFixed(1)}%`);
    bdpo.logSuccess("MA Crossover Strategy shutdown completed");
  }
}
Key Features:
  • Risk Management: Position sizing based on account risk percentage
  • Stop Loss & Take Profit: Automatic risk/reward management
  • Trailing Stops: Lock in profits as trades move favorably
  • Performance Tracking: Win rate and trade statistics
  • Configurable Parameters: Easy to optimize and customize
Breakout Trading Strategy
Trade breakouts from consolidation patterns using Donchian Channels

Strategy Overview

This strategy identifies consolidation periods and trades breakouts above resistance or below support levels. It uses Donchian Channels to define the breakout levels and includes volatility-based position sizing.

class BreakoutStrategy {
  // Public configuration
  public lookbackPeriod: number = 20;     // Donchian Channel period
  public minConsolidation: number = 10;   // Minimum bars in consolidation
  public volatilityPeriod: number = 14;   // ATR period for volatility
  public riskPercentPerTrade: number = 2; // Risk per trade
  public atrMultiplier: number = 2;       // ATR multiplier for stops
  public maxPositionsPerSide: number = 1; // Max long/short positions
  public enableVolumeFilter: boolean = true;
  public minVolumeRatio: number = 1.5;    // Minimum volume spike for breakout

  // Private state
  private _positions: any[] = [];
  private _donchianUpper: number[] = [];
  private _donchianLower: number[] = [];
  private _atr: number[] = [];
  private _consolidationBars: number = 0;
  private _lastBreakout: string | null = null;

  __init__(): void {
    this._positions = [];
    this._consolidationBars = 0;
    this._lastBreakout = null;
    
    bdpo.logInfo("Breakout Strategy initialized");
    bdpo.logInfo(`Lookback: ${this.lookbackPeriod}, Min Consolidation: ${this.minConsolidation}`);
    bdpo.logInfo(`Risk per trade: ${this.riskPercentPerTrade}%, ATR Multiplier: ${this.atrMultiplier}`);
  }

  __tick__(): any {
    const highs: number[] = bdpo.getHighPrices();
    const lows: number[] = bdpo.getLowPrices();
    const closes: number[] = bdpo.getClosePrices();
    const volumes: number[] = bdpo.getVolumePrices();
    
    if (closes.length < this.lookbackPeriod + this.volatilityPeriod) {
      return { status: "warming_up" };
    }

    const currentHigh = highs[highs.length - 1];
    const currentLow = lows[lows.length - 1];
    const currentClose = closes[closes.length - 1];
    const currentVolume = volumes[volumes.length - 1];

    // Calculate indicators
    this._donchianUpper = this.calculateDonchianUpper(highs, this.lookbackPeriod);
    this._donchianLower = this.calculateDonchianLower(lows, this.lookbackPeriod);
    this._atr = bdpo.atr(highs, lows, closes, this.volatilityPeriod);

    const upperChannel = this._donchianUpper[this._donchianUpper.length - 1];
    const lowerChannel = this._donchianLower[this._donchianLower.length - 1];
    const currentATR = this._atr[this._atr.length - 1];

    // Check for consolidation
    const channelRange = upperChannel - lowerChannel;
    const avgRange = bdpo.mean(this._atr.slice(-20));
    const isConsolidating = channelRange < avgRange * 1.5;

    if (isConsolidating) {
      this._consolidationBars++;
    } else {
      this._consolidationBars = 0;
    }

    // Volume filter
    let volumeConfirmed = true;
    if (this.enableVolumeFilter) {
      const avgVolume = bdpo.mean(volumes.slice(-20));
      volumeConfirmed = currentVolume > avgVolume * this.minVolumeRatio;
    }

    // Check for breakout signals
    let signal: any = null;
    const longPositions = this.getLongPositions();
    const shortPositions = this.getShortPositions();

    // Bullish breakout
    if (currentHigh > upperChannel && 
        this._consolidationBars >= this.minConsolidation && 
        volumeConfirmed && 
        longPositions.length < this.maxPositionsPerSide) {
      
      signal = this.generateLongEntry(currentClose, currentATR);
      this._lastBreakout = 'bullish';
      this._consolidationBars = 0;
    }
    // Bearish breakout
    else if (currentLow < lowerChannel && 
             this._consolidationBars >= this.minConsolidation && 
             volumeConfirmed && 
             shortPositions.length < this.maxPositionsPerSide) {
      
      signal = this.generateShortEntry(currentClose, currentATR);
      this._lastBreakout = 'bearish';
      this._consolidationBars = 0;
    }

    // Exit management
    this.manageExits(currentClose, currentATR);

    // Plot channels
    bdpo.plot("splines", [this._donchianUpper], { splineColor: "#FF5722", splineWidth: 1, skipNan: true }, { title: "Upper Channel" });
    bdpo.plot("splines", [this._donchianLower], { splineColor: "#4CAF50", splineWidth: 1, skipNan: true }, { title: "Lower Channel" });

    return {
      signal: signal,
      indicators: {
        upperChannel: upperChannel,
        lowerChannel: lowerChannel,
        channelRange: channelRange.toFixed(4),
        consolidationBars: this._consolidationBars,
        atr: currentATR.toFixed(4)
      },
      positions: {
        long: longPositions.length,
        short: shortPositions.length
      },
      lastBreakout: this._lastBreakout
    };
  }

  private generateLongEntry(price: number, atr: number): any {
    const stopLoss = price - (atr * this.atrMultiplier);
    const takeProfit = price + (atr * this.atrMultiplier * 2); // 1:2 risk/reward
    const positionSize = this.calculatePositionSize(price, stopLoss);

    try {
      const order = bdpo.orderSend({
        orderType: "BUY",
        lotSize: positionSize,
        stopLoss: stopLoss,
        takeProfit: takeProfit,
        note: "Bullish Breakout Entry"
      });

      if (order) {
        this._positions.push({
          ...order,
          direction: "LONG",
          entryPrice: price,
          stopLoss: stopLoss,
          takeProfit: takeProfit,
          entryTime: Date.now()
        });

        bdpo.logSuccess(`LONG breakout entry: Price=${price.toFixed(4)}, SL=${stopLoss.toFixed(4)}, TP=${takeProfit.toFixed(4)}`);
        
        return { type: "BUY", price: price, stopLoss: stopLoss, takeProfit: takeProfit };
      }
    } catch (error) {
      bdpo.logError(`Failed to place LONG order: ${error.message}`);
    }

    return null;
  }

  private generateShortEntry(price: number, atr: number): any {
    const stopLoss = price + (atr * this.atrMultiplier);
    const takeProfit = price - (atr * this.atrMultiplier * 2); // 1:2 risk/reward
    const positionSize = this.calculatePositionSize(price, stopLoss);

    try {
      const order = bdpo.orderSend({
        orderType: "SELL",
        lotSize: positionSize,
        stopLoss: stopLoss,
        takeProfit: takeProfit,
        note: "Bearish Breakout Entry"
      });

      if (order) {
        this._positions.push({
          ...order,
          direction: "SHORT",
          entryPrice: price,
          stopLoss: stopLoss,
          takeProfit: takeProfit,
          entryTime: Date.now()
        });

        bdpo.logSuccess(`SHORT breakout entry: Price=${price.toFixed(4)}, SL=${stopLoss.toFixed(4)}, TP=${takeProfit.toFixed(4)}`);
        
        return { type: "SELL", price: price, stopLoss: stopLoss, takeProfit: takeProfit };
      }
    } catch (error) {
      bdpo.logError(`Failed to place SHORT order: ${error.message}`);
    }

    return null;
  }

  private calculatePositionSize(entryPrice: number, stopLoss: number): number {
    const accountBalance = bdpo.getBalance();
    const riskAmount = accountBalance * (this.riskPercentPerTrade / 100);
    const riskPerUnit = Math.abs(entryPrice - stopLoss);
    
    const positionSize = riskAmount / riskPerUnit;
    
    const minSize = bdpo.getMinQty() || 0.01;
    const maxSize = bdpo.getMaxQty() || 10;
    
    return Math.max(minSize, Math.min(maxSize, positionSize));
  }

  private calculateDonchianUpper(highs: number[], period: number): number[] {
    const result: number[] = [];
    
    for (let i = period - 1; i < highs.length; i++) {
      const periodHighs = highs.slice(i - period + 1, i + 1);
      result.push(Math.max(...periodHighs));
    }
    
    return result;
  }

  private calculateDonchianLower(lows: number[], period: number): number[] {
    const result: number[] = [];
    
    for (let i = period - 1; i < lows.length; i++) {
      const periodLows = lows.slice(i - period + 1, i + 1);
      result.push(Math.min(...periodLows));
    }
    
    return result;
  }

  private manageExits(currentPrice: number, currentATR: number): void {
    // Implementation for exit management
    const openPositions = this.getOpenPositions();
    
    openPositions.forEach(position => {
      const holdingTime = Date.now() - position.entryTime;
      const holdingHours = holdingTime / (1000 * 60 * 60);
      
      // Exit if position held too long without progress
      if (holdingHours > 24) {
        this.closePosition(position, "Time-based exit");
      }
    });
  }

  private closePosition(position: any, reason: string): void {
    try {
      const currentPrice = position.direction === "LONG" ? bdpo.getBid() : bdpo.getAsk();
      const closeOrder = bdpo.orderClose(position.ticket, position.volume, currentPrice, 3);
      
      if (closeOrder) {
        bdpo.logInfo(`Position closed: ${reason}`);
        this._positions = this._positions.filter(p => p.ticket !== position.ticket);
      }
    } catch (error) {
      bdpo.logError(`Failed to close position: ${error.message}`);
    }
  }

  private getLongPositions(): any[] {
    return this._positions.filter(pos => pos.direction === "LONG" && (pos.closeTime === 0 || pos.closeTime === undefined));
  }

  private getShortPositions(): any[] {
    return this._positions.filter(pos => pos.direction === "SHORT" && (pos.closeTime === 0 || pos.closeTime === undefined));
  }

  private getOpenPositions(): any[] {
    return this._positions.filter(pos => pos.closeTime === 0 || pos.closeTime === undefined);
  }

  __shutdown__(): void {
    const openPositions = this.getOpenPositions();
    
    openPositions.forEach(position => {
      this.closePosition(position, "Strategy shutdown");
    });

    bdpo.logSuccess("Breakout Strategy shutdown completed");
  }
}

Mean Reversion Strategies

RSI Mean Reversion Strategy
Trade oversold/overbought conditions with Bollinger Bands confirmation

Strategy Overview

This mean reversion strategy uses RSI to identify oversold and overbought conditions, with Bollinger Bands providing additional confirmation. It's designed for range-bound markets and includes smart position sizing.

class RSIMeanReversionStrategy {
  // Public parameters
  public rsiPeriod: number = 14;
  public rsiOversold: number = 30;
  public rsiOverbought: number = 70;
  public bbPeriod: number = 20;
  public bbStdDev: number = 2;
  public riskPercentPerTrade: number = 1.5;
  public maxPositions: number = 2;
  public requireBBConfirmation: boolean = true;
  public enableScaleIn: boolean = true;     // Scale into positions
  public maxScaleIns: number = 3;

  // Private state
  private _positions: any[] = [];
  private _rsi: number[] = [];
  private _bbands: any = null;
  private _scaleInCount: Map = new Map();

  __init__(): void {
    this._positions = [];
    this._scaleInCount.clear();
    
    bdpo.logInfo("RSI Mean Reversion Strategy initialized");
    bdpo.logInfo(`RSI: ${this.rsiPeriod}, Oversold: ${this.rsiOversold}, Overbought: ${this.rsiOverbought}`);
    bdpo.logInfo(`BB: ${this.bbPeriod} period, ${this.bbStdDev} std dev`);
  }

  __tick__(): any {
    const closes: number[] = bdpo.getClosePrices();
    const currentPrice = closes[closes.length - 1];
    
    if (closes.length < Math.max(this.rsiPeriod, this.bbPeriod) + 1) {
      return { status: "warming_up" };
    }

    // Calculate indicators
    this._rsi = bdpo.rsi(closes, this.rsiPeriod);
    this._bbands = bdpo.bbands(closes, this.bbPeriod, this.bbStdDev);

    const currentRSI = this._rsi[this._rsi.length - 1];
    const prevRSI = this._rsi[this._rsi.length - 2];
    const upperBB = this._bbands.upper[this._bbands.upper.length - 1];
    const lowerBB = this._bbands.lower[this._bbands.lower.length - 1];
    const middleBB = this._bbands.middle[this._bbands.middle.length - 1];

    let signal: any = null;
    const openPositions = this.getOpenPositions();

    // Entry signals
    if (openPositions.length < this.maxPositions) {
      // Oversold condition - potential buy
      if (currentRSI < this.rsiOversold && prevRSI >= this.rsiOversold) {
        const bbConfirmed = !this.requireBBConfirmation || currentPrice <= lowerBB;
        
        if (bbConfirmed) {
          signal = this.generateBuySignal(currentPrice, middleBB);
        }
      }
      // Overbought condition - potential sell
      else if (currentRSI > this.rsiOverbought && prevRSI <= this.rsiOverbought) {
        const bbConfirmed = !this.requireBBConfirmation || currentPrice >= upperBB;
        
        if (bbConfirmed) {
          signal = this.generateSellSignal(currentPrice, middleBB);
        }
      }
    }

    // Scale-in management
    if (this.enableScaleIn) {
      this.manageScaleIns(currentPrice, currentRSI, upperBB, lowerBB);
    }

    // Exit management
    this.manageExits(currentPrice, currentRSI, middleBB);

    // Plot indicators
    bdpo.plot("splines", [this._rsi], { splineColor: "#9C27B0", splineWidth: 2, skipNan: true }, { title: `RSI ${this.rsiPeriod}` });
    bdpo.plot("splines", [this._bbands.upper], { splineColor: "#2196F3", splineWidth: 1, skipNan: true }, { title: "BB Upper" });
    bdpo.plot("splines", [this._bbands.lower], { splineColor: "#2196F3", splineWidth: 1, skipNan: true }, { title: "BB Lower" });
    bdpo.plot("splines", [this._bbands.middle], { splineColor: "#2196F3", splineWidth: 1, skipNan: true }, { title: "BB Middle" });

    return {
      signal: signal,
      indicators: {
        rsi: currentRSI.toFixed(2),
        bbUpper: upperBB.toFixed(4),
        bbLower: lowerBB.toFixed(4),
        bbPosition: this.getBBPosition(currentPrice, upperBB, lowerBB, middleBB)
      },
      positions: openPositions.length,
      scaleIns: Array.from(this._scaleInCount.values()).reduce((a, b) => a + b, 0)
    };
  }

  private generateBuySignal(price: number, target: number): any {
    const stopLoss = price * 0.97; // 3% stop loss
    const takeProfit = target; // Target middle BB
    const positionSize = this.calculatePositionSize(price, stopLoss);

    try {
      const order = bdpo.orderSend({
        orderType: "BUY",
        lotSize: positionSize,
        stopLoss: stopLoss,
        takeProfit: takeProfit,
        note: "RSI Oversold Mean Reversion"
      });

      if (order) {
        const positionId = `LONG_${Date.now()}`;
        this._positions.push({
          ...order,
          id: positionId,
          direction: "LONG",
          entryPrice: price,
          stopLoss: stopLoss,
          takeProfit: takeProfit,
          entryTime: Date.now(),
          strategy: "mean_reversion"
        });

        this._scaleInCount.set(positionId, 0);
        bdpo.logSuccess(`BUY signal: RSI oversold entry at ${price.toFixed(4)}`);
        
        return { type: "BUY", price: price, reason: "RSI oversold" };
      }
    } catch (error) {
      bdpo.logError(`Failed to place BUY order: ${error.message}`);
    }

    return null;
  }

  private generateSellSignal(price: number, target: number): any {
    const stopLoss = price * 1.03; // 3% stop loss
    const takeProfit = target; // Target middle BB
    const positionSize = this.calculatePositionSize(price, stopLoss);

    try {
      const order = bdpo.orderSend({
        orderType: "SELL",
        lotSize: positionSize,
        stopLoss: stopLoss,
        takeProfit: takeProfit,
        note: "RSI Overbought Mean Reversion"
      });

      if (order) {
        const positionId = `SHORT_${Date.now()}`;
        this._positions.push({
          ...order,
          id: positionId,
          direction: "SHORT",
          entryPrice: price,
          stopLoss: stopLoss,
          takeProfit: takeProfit,
          entryTime: Date.now(),
          strategy: "mean_reversion"
        });

        this._scaleInCount.set(positionId, 0);
        bdpo.logSuccess(`SELL signal: RSI overbought entry at ${price.toFixed(4)}`);
        
        return { type: "SELL", price: price, reason: "RSI overbought" };
      }
    } catch (error) {
      bdpo.logError(`Failed to place SELL order: ${error.message}`);
    }

    return null;
  }

  private manageScaleIns(currentPrice: number, currentRSI: number, upperBB: number, lowerBB: number): void {
    const openPositions = this.getOpenPositions();
    
    openPositions.forEach(position => {
      const scaleInCount = this._scaleInCount.get(position.id) || 0;
      
      if (scaleInCount < this.maxScaleIns) {
        let shouldScaleIn = false;
        
        if (position.direction === "LONG") {
          // Scale in if RSI gets more oversold or price drops further below lower BB
          shouldScaleIn = (currentRSI < this.rsiOversold - 5) || 
                         (currentPrice < lowerBB * 0.995);
        } else if (position.direction === "SHORT") {
          // Scale in if RSI gets more overbought or price rises further above upper BB
          shouldScaleIn = (currentRSI > this.rsiOverbought + 5) || 
                         (currentPrice > upperBB * 1.005);
        }
        
        if (shouldScaleIn) {
          this.executeScaleIn(position, currentPrice);
        }
      }
    });
  }

  private executeScaleIn(position: any, price: number): void {
    const scaleInSize = position.volume * 0.5; // 50% of original position
    
    try {
      const scaleInOrder = bdpo.orderSend({
        orderType: position.direction === "LONG" ? "BUY" : "SELL",
        lotSize: scaleInSize,
        stopLoss: position.stopLoss,
        takeProfit: position.takeProfit,
        note: `Scale-in for ${position.id}`
      });

      if (scaleInOrder) {
        const currentCount = this._scaleInCount.get(position.id) || 0;
        this._scaleInCount.set(position.id, currentCount + 1);
        
        bdpo.logInfo(`Scale-in executed for ${position.direction} position: Size=${scaleInSize}, Price=${price.toFixed(4)}`);
      }
    } catch (error) {
      bdpo.logError(`Failed to execute scale-in: ${error.message}`);
    }
  }

  private manageExits(currentPrice: number, currentRSI: number, middleBB: number): void {
    const openPositions = this.getOpenPositions();
    
    openPositions.forEach(position => {
      let shouldExit = false;
      let exitReason = "";
      
      if (position.direction === "LONG") {
        // Exit long if RSI normalizes above 50 or price reaches middle BB
        if (currentRSI > 50 || currentPrice >= middleBB) {
          shouldExit = true;
          exitReason = currentRSI > 50 ? "RSI normalized" : "Target reached";
        }
      } else if (position.direction === "SHORT") {
        // Exit short if RSI normalizes below 50 or price reaches middle BB
        if (currentRSI < 50 || currentPrice <= middleBB) {
          shouldExit = true;
          exitReason = currentRSI < 50 ? "RSI normalized" : "Target reached";
        }
      }
      
      if (shouldExit) {
        this.closePosition(position, exitReason);
      }
    });
  }

  private closePosition(position: any, reason: string): void {
    try {
      const currentPrice = position.direction === "LONG" ? bdpo.getBid() : bdpo.getAsk();
      const closeOrder = bdpo.orderClose(position.ticket, position.volume, currentPrice, 3);
      
      if (closeOrder) {
        bdpo.logSuccess(`Position closed: ${reason}`);
        this._positions = this._positions.filter(p => p.id !== position.id);
        this._scaleInCount.delete(position.id);
      }
    } catch (error) {
      bdpo.logError(`Failed to close position: ${error.message}`);
    }
  }

  private calculatePositionSize(entryPrice: number, stopLoss: number): number {
    const accountBalance = bdpo.getBalance();
    const riskAmount = accountBalance * (this.riskPercentPerTrade / 100);
    const riskPerUnit = Math.abs(entryPrice - stopLoss);
    
    const positionSize = riskAmount / riskPerUnit;
    
    const minSize = bdpo.getMinQty() || 0.01;
    const maxSize = bdpo.getMaxQty() || 5;
    
    return Math.max(minSize, Math.min(maxSize, positionSize));
  }

  private getBBPosition(price: number, upper: number, lower: number, middle: number): string {
    if (price > upper) return "Above Upper";
    if (price < lower) return "Below Lower";
    if (price > middle) return "Upper Half";
    return "Lower Half";
  }

  private getOpenPositions(): any[] {
    return this._positions.filter(pos => pos.closeTime === 0 || pos.closeTime === undefined);
  }

  __shutdown__(): void {
    const openPositions = this.getOpenPositions();
    
    openPositions.forEach(position => {
      this.closePosition(position, "Strategy shutdown");
    });

    bdpo.logSuccess("RSI Mean Reversion Strategy shutdown completed");
  }
}

Multi-Timeframe Strategies

Multi-Timeframe Trend Strategy
Align trades with higher timeframe trends using multiple timeframe analysis

Strategy Overview

This strategy analyzes multiple timeframes to ensure trades align with the broader trend. It uses higher timeframes for trend direction and lower timeframes for precise entry timing.

class MultiTimeframeStrategy {
  // Public configuration
  public trendTimeframe: string = "1h";      // Higher timeframe for trend
  public entryTimeframe: string = "15m";     // Lower timeframe for entries
  public trendMAPeriod: number = 50;         // Trend MA period
  public entryMAPeriod: number = 20;         // Entry MA period
  public riskPercentPerTrade: number = 2;
  public maxPositions: number = 2;
  public requireTrendAlignment: boolean = true;

  // Private state
  private _positions: any[] = [];
  private _trendDirection: string | null = null;
  private _lastAnalysis: any = null;

  __init__(): void {
    this._positions = [];
    this._trendDirection = null;
    this._lastAnalysis = null;
    
    bdpo.logInfo("Multi-Timeframe Strategy initialized");
    bdpo.logInfo(`Trend TF: ${this.trendTimeframe}, Entry TF: ${this.entryTimeframe}`);
    bdpo.logInfo(`Trend MA: ${this.trendMAPeriod}, Entry MA: ${this.entryMAPeriod}`);
  }

  __tick__(): any {
    // Get data for both timeframes
    const trendData = bdpo.getTimeframeData(this.trendTimeframe);
    const entryData = bdpo.getTimeframeData(this.entryTimeframe);
    
    if (!trendData || !entryData) {
      return { status: "waiting_for_data" };
    }

    // Analyze higher timeframe trend
    const trendAnalysis = this.analyzeTrend(trendData);
    this._trendDirection = trendAnalysis.direction;

    // Analyze entry timeframe
    const entryAnalysis = this.analyzeEntry(entryData);

    // Generate signals
    let signal: any = null;
    const openPositions = this.getOpenPositions();

    if (openPositions.length < this.maxPositions && this._trendDirection !== "SIDEWAYS") {
      if (this.requireTrendAlignment) {
        // Only trade in direction of higher timeframe trend
        if (this._trendDirection === "BULLISH" && entryAnalysis.signal === "BUY") {
          signal = this.generateBuySignal(entryAnalysis);
        } else if (this._trendDirection === "BEARISH" && entryAnalysis.signal === "SELL") {
          signal = this.generateSellSignal(entryAnalysis);
        }
      } else {
        // Trade any signal
        if (entryAnalysis.signal === "BUY") {
          signal = this.generateBuySignal(entryAnalysis);
        } else if (entryAnalysis.signal === "SELL") {
          signal = this.generateSellSignal(entryAnalysis);
        }
      }
    }

    // Manage exits
    this.manageExits(entryAnalysis);

    this._lastAnalysis = {
      trend: trendAnalysis,
      entry: entryAnalysis,
      timestamp: Date.now()
    };

    return {
      signal: signal,
      trendDirection: this._trendDirection,
      trendStrength: trendAnalysis.strength,
      entrySignal: entryAnalysis.signal,
      entryStrength: entryAnalysis.strength,
      positions: openPositions.length,
      alignment: this._trendDirection === entryAnalysis.trendBias ? "ALIGNED" : "DIVERGENT"
    };
  }

  private analyzeTrend(trendData: any[]): any {
    const closes = bdpo.getClose(trendData);
    const highs = bdpo.getHigh(trendData);
    const lows = bdpo.getLow(trendData);
    
    if (closes.length < this.trendMAPeriod + 20) {
      return { direction: "UNKNOWN", strength: 0 };
    }

    const trendMA = bdpo.ema(closes, this.trendMAPeriod);
    const currentPrice = closes[closes.length - 1];
    const currentMA = trendMA[trendMA.length - 1];
    const prevMA = trendMA[trendMA.length - 2];

    // Calculate trend strength using various factors
    const maSlope = (currentMA - prevMA) / prevMA * 100;
    const priceToMADistance = (currentPrice - currentMA) / currentMA * 100;
    
    // Higher highs and higher lows for uptrend
    const recentHighs = highs.slice(-10);
    const recentLows = lows.slice(-10);
    const hhhl = this.calculateTrendPattern(recentHighs, recentLows);
    
    let direction: string;
    let strength: number;

    if (currentPrice > currentMA && maSlope > 0.1 && hhhl.score > 0.6) {
      direction = "BULLISH";
      strength = Math.min(100, (Math.abs(maSlope) + Math.abs(priceToMADistance) + hhhl.score * 50));
    } else if (currentPrice < currentMA && maSlope < -0.1 && hhhl.score < -0.6) {
      direction = "BEARISH";
      strength = Math.min(100, (Math.abs(maSlope) + Math.abs(priceToMADistance) + Math.abs(hhhl.score) * 50));
    } else {
      direction = "SIDEWAYS";
      strength = 0;
    }

    return {
      direction: direction,
      strength: strength,
      maSlope: maSlope,
      priceToMA: priceToMADistance,
      patternScore: hhhl.score
    };
  }

  private analyzeEntry(entryData: any[]): any {
    const closes = bdpo.getClose(entryData);
    const currentPrice = closes[closes.length - 1];
    
    if (closes.length < this.entryMAPeriod + 5) {
      return { signal: "WAIT", strength: 0, trendBias: "UNKNOWN" };
    }

    const entryMA = bdpo.ema(closes, this.entryMAPeriod);
    const rsi = bdpo.rsi(closes, 14);
    
    const currentMA = entryMA[entryMA.length - 1];
    const prevMA = entryMA[entryMA.length - 2];
    const currentRSI = rsi[rsi.length - 1];
    
    // Determine entry timeframe bias
    const trendBias = currentPrice > currentMA ? "BULLISH" : "BEARISH";
    
    let signal: string = "WAIT";
    let strength: number = 0;

    // Look for pullback opportunities in trend direction
    if (this._trendDirection === "BULLISH") {
      // Look for dips to buy in uptrend
      if (currentPrice < currentMA && currentPrice > prevMA && currentRSI < 60) {
        signal = "BUY";
        strength = 60 + (60 - currentRSI); // Stronger signal if more oversold
      }
    } else if (this._trendDirection === "BEARISH") {
      // Look for rallies to sell in downtrend
      if (currentPrice > currentMA && currentPrice < prevMA && currentRSI > 40) {
        signal = "SELL";
        strength = 40 + (currentRSI - 40); // Stronger signal if more overbought
      }
    }

    return {
      signal: signal,
      strength: strength,
      trendBias: trendBias,
      rsi: currentRSI,
      priceToMA: ((currentPrice - currentMA) / currentMA * 100)
    };
  }

  private calculateTrendPattern(highs: number[], lows: number[]): any {
    // Simplified trend pattern calculation
    let bullishPoints = 0;
    let bearishPoints = 0;
    
    for (let i = 1; i < highs.length; i++) {
      if (highs[i] > highs[i-1]) bullishPoints++;
      else bearishPoints++;
      
      if (lows[i] > lows[i-1]) bullishPoints++;
      else bearishPoints++;
    }
    
    const totalPoints = bullishPoints + bearishPoints;
    const score = totalPoints > 0 ? (bullishPoints - bearishPoints) / totalPoints : 0;
    
    return { score: score, bullish: bullishPoints, bearish: bearishPoints };
  }

  private generateBuySignal(entryAnalysis: any): any {
    const currentPrice = bdpo.getAsk();
    const stopLoss = currentPrice * 0.98; // 2% stop loss
    const takeProfit = currentPrice * 1.06; // 3:1 risk/reward
    const positionSize = this.calculatePositionSize(currentPrice, stopLoss);

    try {
      const order = bdpo.orderSend({
        orderType: "BUY",
        lotSize: positionSize,
        stopLoss: stopLoss,
        takeProfit: takeProfit,
        note: `Multi-TF Bullish Entry (Strength: ${entryAnalysis.strength})`
      });

      if (order) {
        this._positions.push({
          ...order,
          direction: "LONG",
          entryPrice: currentPrice,
          entryAnalysis: entryAnalysis,
          entryTime: Date.now()
        });

        bdpo.logSuccess(`Multi-TF BUY: Price=${currentPrice.toFixed(4)}, Trend=${this._trendDirection}, Strength=${entryAnalysis.strength}`);
        
        return { 
          type: "BUY", 
          price: currentPrice, 
          trendAlignment: true,
          strength: entryAnalysis.strength
        };
      }
    } catch (error) {
      bdpo.logError(`Failed to place BUY order: ${error.message}`);
    }

    return null;
  }

  private generateSellSignal(entryAnalysis: any): any {
    const currentPrice = bdpo.getBid();
    const stopLoss = currentPrice * 1.02; // 2% stop loss
    const takeProfit = currentPrice * 0.94; // 3:1 risk/reward
    const positionSize = this.calculatePositionSize(currentPrice, stopLoss);

    try {
      const order = bdpo.orderSend({
        orderType: "SELL",
        lotSize: positionSize,
        stopLoss: stopLoss,
        takeProfit: takeProfit,
        note: `Multi-TF Bearish Entry (Strength: ${entryAnalysis.strength})`
      });

      if (order) {
        this._positions.push({
          ...order,
          direction: "SHORT",
          entryPrice: currentPrice,
          entryAnalysis: entryAnalysis,
          entryTime: Date.now()
        });

        bdpo.logSuccess(`Multi-TF SELL: Price=${currentPrice.toFixed(4)}, Trend=${this._trendDirection}, Strength=${entryAnalysis.strength}`);
        
        return { 
          type: "SELL", 
          price: currentPrice, 
          trendAlignment: true,
          strength: entryAnalysis.strength
        };
      }
    } catch (error) {
      bdpo.logError(`Failed to place SELL order: ${error.message}`);
    }

    return null;
  }

  private manageExits(entryAnalysis: any): void {
    const openPositions = this.getOpenPositions();
    
    openPositions.forEach(position => {
      let shouldExit = false;
      let exitReason = "";
      
      // Exit if trend changes against position
      if (position.direction === "LONG" && this._trendDirection === "BEARISH") {
        shouldExit = true;
        exitReason = "Trend reversal";
      } else if (position.direction === "SHORT" && this._trendDirection === "BULLISH") {
        shouldExit = true;
        exitReason = "Trend reversal";
      }
      
      // Exit if RSI shows extreme opposite conditions
      if (position.direction === "LONG" && entryAnalysis.rsi > 80) {
        shouldExit = true;
        exitReason = "RSI overbought";
      } else if (position.direction === "SHORT" && entryAnalysis.rsi < 20) {
        shouldExit = true;
        exitReason = "RSI oversold";
      }
      
      if (shouldExit) {
        this.closePosition(position, exitReason);
      }
    });
  }

  private closePosition(position: any, reason: string): void {
    try {
      const currentPrice = position.direction === "LONG" ? bdpo.getBid() : bdpo.getAsk();
      const closeOrder = bdpo.orderClose(position.ticket, position.volume, currentPrice, 3);
      
      if (closeOrder) {
        bdpo.logInfo(`Multi-TF position closed: ${reason}`);
        this._positions = this._positions.filter(p => p.ticket !== position.ticket);
      }
    } catch (error) {
      bdpo.logError(`Failed to close position: ${error.message}`);
    }
  }

  private calculatePositionSize(entryPrice: number, stopLoss: number): number {
    const accountBalance = bdpo.getBalance();
    const riskAmount = accountBalance * (this.riskPercentPerTrade / 100);
    const riskPerUnit = Math.abs(entryPrice - stopLoss);
    
    const positionSize = riskAmount / riskPerUnit;
    
    const minSize = bdpo.getMinQty() || 0.01;
    const maxSize = bdpo.getMaxQty() || 5;
    
    return Math.max(minSize, Math.min(maxSize, positionSize));
  }

  private getOpenPositions(): any[] {
    return this._positions.filter(pos => pos.closeTime === 0 || pos.closeTime === undefined);
  }

  __shutdown__(): void {
    const openPositions = this.getOpenPositions();
    
    openPositions.forEach(position => {
      this.closePosition(position, "Strategy shutdown");
    });

    bdpo.logSuccess("Multi-Timeframe Strategy shutdown completed");
  }
}
Important Notes:
  • Risk Management: All strategies include position sizing based on account risk
  • Market Conditions: Test strategies in different market conditions (trending, ranging, volatile)
  • Parameter Optimization: Adjust parameters based on your market and trading style
  • Live Trading: Always test strategies on demo accounts before live implementation
  • Performance Monitoring: Track win rates, drawdowns, and risk-adjusted returns

Indicator Examples

Simple and practical indicator implementations for beginners

About Indicators: These are ultra-simple indicator examples designed for beginners. Each indicator has only three functions: __init__(), __tick__(), and __shutdown__(). They use basic bdpo.plot() calls to display data on the chart.

Simple Momentum Indicators

Simple RSI Indicator
A basic RSI indicator with clear buy/sell signals

Indicator Overview

This simple RSI indicator calculates the Relative Strength Index and shows clear signals when RSI crosses overbought or oversold levels. Perfect for beginners learning technical analysis.

class SimpleRSI {
  public rsiLength: number = 14;

  __init__(): void {
    console.log("RSI Indicator started");
  }

  __tick__(): any {
    // Calculate RSI with configurable length
    let candles = bdpo.getChartData()
    let closes = bdpo.getClose(candles)
    const rsi = bdpo.rsi(closes, this.rsiLength);
    
    // Plot the RSI line
    bdpo.plot("spline", rsi);
    
  }

  __shutdown__(): void {
    console.log("RSI Indicator stopped");
  }
}
How it works:
  • RSI Calculation: Uses 14-period RSI by default
  • Buy Signal: When RSI drops below 30 (oversold)
  • Sell Signal: When RSI rises above 70 (overbought)
  • Visualization: Plots RSI with reference lines in separate panel
Simple Moving Average Crossover
Two moving averages with crossover signals

Indicator Overview

This indicator shows two moving averages and generates signals when they cross over. When the fast MA crosses above the slow MA, it's a bullish signal. When it crosses below, it's bearish.

class SimpleMovingAverageCrossover {
  __init__(): void {
    console.log("MA Crossover Indicator started");
  }

  __tick__(): any {
    // Calculate fast and slow moving averages
    const fastMA = bdpo.sma(bdpo.getClose(), 10);
    const slowMA = bdpo.sma(bdpo.getClose(), 20);

    // Plot both lines
    bdpo.plot("splines", [fastMA]);
    bdpo.plot("splines", [slowMA]);

    // Check for crossover signals
    const currentFast = fastMA[fastMA.length - 1];
    const currentSlow = slowMA[slowMA.length - 1];
    
    if (currentFast > currentSlow) {
      console.log("BUY signal - Fast MA above Slow MA");
    } else {
      console.log("SELL signal - Fast MA below Slow MA");
    }

    return { fastMA: currentFast, slowMA: currentSlow };
  }

  __shutdown__(): void {
    console.log("MA Crossover Indicator stopped");
  }
}
How it works:
  • Fast MA: Short-term moving average (10 periods)
  • Slow MA: Long-term moving average (20 periods)
  • Bullish Signal: When fast MA crosses above slow MA
  • Bearish Signal: When fast MA crosses below slow MA
Simple Price Line
Basic price plotting with direction signals

Indicator Overview

This is the simplest possible indicator - it just plots the closing price with signals showing price direction.

class SimplePriceLine {
  __init__(): void {
    console.log("Price Line Indicator started");
  }

  __tick__(): any {
    // Get price data
    const prices = bdpo.getClose();
    
    // Plot the price line
    bdpo.plot("splines", [prices]);

    // Show if price is going up or down
    const currentPrice = prices[prices.length - 1];
    const previousPrice = prices[prices.length - 2];
    
    if (currentPrice > previousPrice) {
      console.log("Price going UP");
    } else if (currentPrice < previousPrice) {
      console.log("Price going DOWN");
    }

    return { price: currentPrice };
  }

  __shutdown__(): void {
    console.log("Price Line Indicator stopped");
  }
}
How it works:
  • Simple Plot: Shows the closing price as a line
  • Price Direction: Compares current price to previous price
  • Perfect for beginners: The most basic indicator possible
Getting Started Tips:
  • Start with Simple Price Line: The most basic example shows how indicators work
  • Three Functions Only: Every indicator needs __init__(), __tick__(), and __shutdown__()
  • Use bdpo.plot(): This single function displays all your data on charts
  • Console.log for Signals: Simple way to show buy/sell alerts
  • Copy and Modify: Start with these examples and change the logic to fit your needs

Best Practices

Guidelines for effective indicator and strategy development

Development Best Practices

Key Principles:
  • Keep It Simple: Start with basic indicators and gradually add complexity
  • Test Thoroughly: Always backtest on historical data before live implementation
  • Risk Management: Include proper position sizing and stop-loss mechanisms
  • Document Your Code: Add clear comments explaining your logic
  • Parameter Optimization: Don't over-optimize - ensure robustness across different market conditions
Dynamic Bollinger Bands
Adaptive Bollinger Bands that adjust to market volatility conditions

Indicator Overview

This enhanced Bollinger Bands indicator automatically adjusts its parameters based on market volatility, providing more accurate signals in both trending and ranging markets.

class DynamicBollingerBands {
  // Public configuration
  public basePeriod: number = 20;           // Base period for moving average
  public baseStdDev: number = 2;            // Base standard deviation multiplier
  public volatilityPeriod: number = 14;     // Period for volatility calculation
  public adaptiveEnabled: boolean = true;   // Enable adaptive adjustment
  public minPeriod: number = 10;            // Minimum period allowed
  public maxPeriod: number = 50;            // Maximum period allowed
  public minStdDev: number = 1;             // Minimum std dev multiplier
  public maxStdDev: number = 3;             // Maximum std dev multiplier
  public showSqueezeSignals: boolean = true; // Show squeeze/expansion signals

  // Private state
  private _closes: number[] = [];
  private _volatility: number[] = [];
  private _adaptivePeriod: number = 20;
  private _adaptiveStdDev: number = 2;
  private _upperBand: number[] = [];
  private _lowerBand: number[] = [];
  private _middleBand: number[] = [];
  private _bandwidth: number[] = [];
  private _squeezeState: boolean = false;
  private _lastSqueezeBar: number = -1;

  __init__(): void {
    this._closes = [];
    this._volatility = [];
    this._adaptivePeriod = this.basePeriod;
    this._adaptiveStdDev = this.baseStdDev;
    this._upperBand = [];
    this._lowerBand = [];
    this._middleBand = [];
    this._bandwidth = [];
    this._squeezeState = false;
    this._lastSqueezeBar = -1;
    
    bdpo.logInfo("Dynamic Bollinger Bands initialized");
    bdpo.logInfo(`Base Period: ${this.basePeriod}, Base StdDev: ${this.baseStdDev}`);
  }

  __tick__(): any {
    this._closes = bdpo.getClose();
    
    if (this._closes.length < Math.max(this.basePeriod, this.volatilityPeriod) + 5) {
      return { status: "warming_up" };
    }

    // Calculate current volatility
    this.calculateVolatility();
    
    // Adjust parameters based on volatility if adaptive mode is enabled
    if (this.adaptiveEnabled) {
      this.adjustParameters();
    }

    // Calculate Bollinger Bands with current parameters
    this.calculateBands();

    // Calculate bandwidth and detect squeeze/expansion
    this.calculateBandwidth();
    const squeezeSignal = this.detectSqueezeExpansion();

    // Plot bands and signals
    this.plotBands();
    
    if (squeezeSignal) {
      this.plotSqueezeSignals(squeezeSignal);
    }

    const currentClose = this._closes[this._closes.length - 1];
    const currentUpper = this._upperBand[this._upperBand.length - 1];
    const currentLower = this._lowerBand[this._lowerBand.length - 1];
    const currentMiddle = this._middleBand[this._middleBand.length - 1];

    return {
      upperBand: currentUpper,
      lowerBand: currentLower,
      middleBand: currentMiddle,
      bandwidth: this._bandwidth[this._bandwidth.length - 1],
      adaptivePeriod: this._adaptivePeriod,
      adaptiveStdDev: this._adaptiveStdDev,
      position: this.getBandPosition(currentClose, currentUpper, currentLower, currentMiddle),
      squeeze: this._squeezeState,
      signal: squeezeSignal
    };
  }

  private calculateVolatility(): void {
    // Calculate ATR-based volatility
    const highs = bdpo.getHigh();
    const lows = bdpo.getLow();
    this._volatility = bdpo.atr(highs, lows, this._closes, this.volatilityPeriod);
  }

  private adjustParameters(): void {
    if (this._volatility.length < 2) return;

    const currentVol = this._volatility[this._volatility.length - 1];
    const avgVol = bdpo.mean(this._volatility.slice(-20)); // 20-period average volatility
    
    if (avgVol === 0) return;
    
    const volRatio = currentVol / avgVol;

    // Adjust period: shorter in high volatility, longer in low volatility
    const periodAdjustment = 1 / volRatio;
    this._adaptivePeriod = Math.round(this.basePeriod * periodAdjustment);
    this._adaptivePeriod = Math.max(this.minPeriod, Math.min(this.maxPeriod, this._adaptivePeriod));

    // Adjust standard deviation: higher in high volatility, lower in low volatility
    const stdDevAdjustment = Math.sqrt(volRatio);
    this._adaptiveStdDev = this.baseStdDev * stdDevAdjustment;
    this._adaptiveStdDev = Math.max(this.minStdDev, Math.min(this.maxStdDev, this._adaptiveStdDev));
  }

  private calculateBands(): void {
    // Calculate bands using current adaptive parameters
    const bands = bdpo.bbands(this._closes, this._adaptivePeriod, this._adaptiveStdDev);
    
    this._upperBand = bands.upper;
    this._lowerBand = bands.lower;
    this._middleBand = bands.middle;
  }

  private calculateBandwidth(): void {
    this._bandwidth = [];
    
    for (let i = 0; i < this._upperBand.length; i++) {
      const bandwidth = (this._upperBand[i] - this._lowerBand[i]) / this._middleBand[i] * 100;
      this._bandwidth.push(bandwidth);
    }
  }

  private detectSqueezeExpansion(): any {
    if (this._bandwidth.length < 20) return null;

    const currentBandwidth = this._bandwidth[this._bandwidth.length - 1];
    const avgBandwidth = bdpo.mean(this._bandwidth.slice(-20));
    const bandwidthPercentile = this.calculatePercentile(this._bandwidth.slice(-50), currentBandwidth);

    const currentBar = this._closes.length - 1;
    
    // Detect squeeze (low bandwidth)
    if (!this._squeezeState && bandwidthPercentile < 20) {
      this._squeezeState = true;
      this._lastSqueezeBar = currentBar;
      
      bdpo.logInfo(`🔥 Bollinger Band SQUEEZE detected at bar ${currentBar}`);
      bdpo.logInfo(`Bandwidth: ${currentBandwidth.toFixed(4)} (${bandwidthPercentile.toFixed(1)}th percentile)`);
      
      return {
        type: 'squeeze',
        bandwidth: currentBandwidth,
        percentile: bandwidthPercentile
      };
    }
    
    // Detect expansion (breaking out of squeeze)
    if (this._squeezeState && bandwidthPercentile > 50) {
      this._squeezeState = false;
      
      const squeezeLength = currentBar - this._lastSqueezeBar;
      bdpo.logSuccess(`💥 Bollinger Band EXPANSION detected!`);
      bdpo.logInfo(`Squeeze lasted ${squeezeLength} bars`);
      bdpo.logInfo(`Bandwidth: ${currentBandwidth.toFixed(4)} (${bandwidthPercentile.toFixed(1)}th percentile)`);
      
      return {
        type: 'expansion',
        bandwidth: currentBandwidth,
        percentile: bandwidthPercentile,
        squeezeLength: squeezeLength
      };
    }

    return null;
  }

  private calculatePercentile(data: number[], value: number): number {
    const sorted = [...data].sort((a, b) => a - b);
    const index = sorted.findIndex(v => v >= value);
    return index >= 0 ? (index / sorted.length) * 100 : 100;
  }

  private getBandPosition(price: number, upper: number, lower: number, middle: number): string {
    if (price > upper) return "Above Upper Band";
    if (price < lower) return "Below Lower Band";
    if (price > middle) return "Upper Half";
    return "Lower Half";
  }

  private plotBands(): void {
    // Plot adaptive Bollinger Bands
    bdpo.plot("splines", [this._upperBand], {
      color: "#2196F3",
      width: 2,
      lineStyle: "solid"
    }, {
      title: `Dynamic Upper Band (${this._adaptivePeriod}, ${this._adaptiveStdDev.toFixed(2)})`
    });

    bdpo.plot("splines", [this._middleBand], {
      color: "#FF9800",
      width: 2,
      lineStyle: "solid"
    }, {
      title: `Dynamic Middle Band (${this._adaptivePeriod})`
    });

    bdpo.plot("splines", [this._lowerBand], {
      color: "#2196F3",
      width: 2,
      lineStyle: "solid"
    }, {
      title: `Dynamic Lower Band (${this._adaptivePeriod}, ${this._adaptiveStdDev.toFixed(2)})`
    });

    // Plot bandwidth in separate panel
    bdpo.plot("splines", [this._bandwidth], {
      color: "#9C27B0",
      width: 2
    }, {
      title: "Bandwidth %",
      panel: "bandwidth_panel"
    });
  }

  private plotSqueezeSignals(signal: any): void {
    if (!this.showSqueezeSignals) return;

    const signalArray = new Array(this._closes.length).fill(null);
    signalArray[signalArray.length - 1] = signal.type === 'squeeze' ? 
      this._lowerBand[this._lowerBand.length - 1] : 
      this._upperBand[this._upperBand.length - 1];

    bdpo.plot("trades", signalArray.map((sig, i) => 
      sig ? [signal.type === 'squeeze' ? -1 : 1, sig, signal.type.toUpperCase()] : null
    ).filter(Boolean), {
      color: signal.type === 'squeeze' ? "#FF5722" : "#4CAF50",
      size: 10,
      shape: signal.type === 'squeeze' ? "circle" : "triangle"
    }, {
      title: `${signal.type.charAt(0).toUpperCase() + signal.type.slice(1)} Signal`
    });
  }

  __shutdown__(): void {
    const finalBandwidth = this._bandwidth[this._bandwidth.length - 1];
    bdpo.logInfo(`Dynamic Bollinger Bands shutdown - Final bandwidth: ${finalBandwidth?.toFixed(4) || 'N/A'}`);
    bdpo.logInfo(`Final parameters - Period: ${this._adaptivePeriod}, StdDev: ${this._adaptiveStdDev.toFixed(2)}`);
  }
}
Support and Resistance Levels
Automatically identifies and plots key support and resistance levels

Indicator Overview

This indicator uses pivot point analysis to automatically identify significant support and resistance levels based on historical price action, helping traders identify key decision points.

class SupportResistanceLevels {
  // Public configuration
  public pivotLookback: number = 5;         // Bars to look back for pivot identification
  public strengthThreshold: number = 3;     // Minimum touches to confirm level
  public maxLevels: number = 10;            // Maximum number of levels to track
  public levelTolerance: number = 0.002;    // Price tolerance for level confirmation (0.2%)
  public minLevelSpacing: number = 0.01;    // Minimum spacing between levels (1%)
  public levelExpiryBars: number = 100;     // Remove levels after N bars without touch
  public showBreakouts: boolean = true;     // Show breakout signals
  public showRejections: boolean = true;    // Show rejection signals

  // Private state
  private _pivotHighs: Array<{price: number, bar: number, strength: number}> = [];
  private _pivotLows: Array<{price: number, bar: number, strength: number}> = [];
  private _supportLevels: Array<{price: number, strength: number, touches: number[], lastTouch: number}> = [];
  private _resistanceLevels: Array<{price: number, strength: number, touches: number[], lastTouch: number}> = [];
  private _signals: Array<{type: string, price: number, bar: number, level: number}> = [];

  __init__(): void {
    this._pivotHighs = [];
    this._pivotLows = [];
    this._supportLevels = [];
    this._resistanceLevels = [];
    this._signals = [];
    
    bdpo.logInfo("Support and Resistance Levels indicator initialized");
    bdpo.logInfo(`Pivot Lookback: ${this.pivotLookback}, Strength Threshold: ${this.strengthThreshold}`);
  }

  __tick__(): any {
    const closes = bdpo.getClose();
    const highs = bdpo.getHigh();
    const lows = bdpo.getLow();
    const currentBar = closes.length - 1;

    if (currentBar < this.pivotLookback * 2 + 5) {
      return { status: "warming_up" };
    }

    // Identify new pivot points
    this.identifyPivots(highs, lows, currentBar);
    
    // Update support and resistance levels
    this.updateLevels(closes[currentBar], currentBar);
    
    // Check for signals
    const signal = this.checkForSignals(closes[currentBar], highs[currentBar], lows[currentBar], currentBar);
    
    // Plot levels and signals
    this.plotLevels();
    
    if (signal) {
      this.plotSignal(signal);
    }

    return {
      supportLevels: this._supportLevels.map(level => ({
        price: level.price,
        strength: level.strength,
        touches: level.touches.length
      })),
      resistanceLevels: this._resistanceLevels.map(level => ({
        price: level.price,
        strength: level.strength,
        touches: level.touches.length
      })),
      totalLevels: this._supportLevels.length + this._resistanceLevels.length,
      lastSignal: signal
    };
  }

  private identifyPivots(highs: number[], lows: number[], currentBar: number): void {
    // Check for pivot high
    if (currentBar >= this.pivotLookback * 2) {
      const pivotBar = currentBar - this.pivotLookback;
      const pivotHigh = highs[pivotBar];
      let isValidPivotHigh = true;

      // Check if this high is higher than surrounding bars
      for (let i = 1; i <= this.pivotLookback; i++) {
        if (highs[pivotBar - i] >= pivotHigh || highs[pivotBar + i] >= pivotHigh) {
          isValidPivotHigh = false;
          break;
        }
      }

      if (isValidPivotHigh) {
        this._pivotHighs.push({
          price: pivotHigh,
          bar: pivotBar,
          strength: this.calculatePivotStrength(highs, pivotBar, 'high')
        });
      }

      // Check for pivot low
      const pivotLow = lows[pivotBar];
      let isValidPivotLow = true;

      for (let i = 1; i <= this.pivotLookback; i++) {
        if (lows[pivotBar - i] <= pivotLow || lows[pivotBar + i] <= pivotLow) {
          isValidPivotLow = false;
          break;
        }
      }

      if (isValidPivotLow) {
        this._pivotLows.push({
          price: pivotLow,
          bar: pivotBar,
          strength: this.calculatePivotStrength(lows, pivotBar, 'low')
        });
      }
    }
  }

  private calculatePivotStrength(data: number[], pivotBar: number, type: 'high' | 'low'): number {
    let strength = 1;
    const pivotPrice = data[pivotBar];
    
    // Calculate strength based on how much the pivot stands out
    for (let i = 1; i <= this.pivotLookback; i++) {
      const leftPrice = data[pivotBar - i];
      const rightPrice = data[pivotBar + i];
      
      if (type === 'high') {
        const leftDiff = (pivotPrice - leftPrice) / leftPrice;
        const rightDiff = (pivotPrice - rightPrice) / rightPrice;
        strength += (leftDiff + rightDiff) * 100;
      } else {
        const leftDiff = (leftPrice - pivotPrice) / pivotPrice;
        const rightDiff = (rightPrice - pivotPrice) / pivotPrice;
        strength += (leftDiff + rightDiff) * 100;
      }
    }
    
    return Math.max(1, strength);
  }

  private updateLevels(currentPrice: number, currentBar: number): void {
    // Process pivot highs into resistance levels
    this._pivotHighs.forEach(pivot => {
      let levelFound = false;
      
      for (let level of this._resistanceLevels) {
        const priceDiff = Math.abs(pivot.price - level.price) / level.price;
        
        if (priceDiff <= this.levelTolerance) {
          // Update existing level
          level.strength += pivot.strength;
          level.touches.push(pivot.bar);
          level.lastTouch = pivot.bar;
          levelFound = true;
          break;
        }
      }
      
      if (!levelFound && this._resistanceLevels.length < this.maxLevels) {
        // Create new resistance level
        this._resistanceLevels.push({
          price: pivot.price,
          strength: pivot.strength,
          touches: [pivot.bar],
          lastTouch: pivot.bar
        });
      }
    });

    // Process pivot lows into support levels
    this._pivotLows.forEach(pivot => {
      let levelFound = false;
      
      for (let level of this._supportLevels) {
        const priceDiff = Math.abs(pivot.price - level.price) / level.price;
        
        if (priceDiff <= this.levelTolerance) {
          level.strength += pivot.strength;
          level.touches.push(pivot.bar);
          level.lastTouch = pivot.bar;
          levelFound = true;
          break;
        }
      }
      
      if (!levelFound && this._supportLevels.length < this.maxLevels) {
        this._supportLevels.push({
          price: pivot.price,
          strength: pivot.strength,
          touches: [pivot.bar],
          lastTouch: pivot.bar
        });
      }
    });

    // Clear processed pivots
    this._pivotHighs = [];
    this._pivotLows = [];

    // Remove expired levels
    this._supportLevels = this._supportLevels.filter(level => 
      currentBar - level.lastTouch < this.levelExpiryBars && level.touches.length >= this.strengthThreshold
    );
    
    this._resistanceLevels = this._resistanceLevels.filter(level => 
      currentBar - level.lastTouch < this.levelExpiryBars && level.touches.length >= this.strengthThreshold
    );

    // Sort levels by strength
    this._supportLevels.sort((a, b) => b.strength - a.strength);
    this._resistanceLevels.sort((a, b) => b.strength - a.strength);
  }

  private checkForSignals(close: number, high: number, low: number, currentBar: number): any {
    // Check for breakouts and rejections
    for (let resistance of this._resistanceLevels) {
      const priceDiff = Math.abs(close - resistance.price) / resistance.price;
      
      if (this.showBreakouts && high > resistance.price && close > resistance.price * (1 + this.levelTolerance)) {
        const signal = {
          type: 'resistance_breakout',
          price: resistance.price,
          bar: currentBar,
          level: resistance.price,
          strength: resistance.strength
        };
        this._signals.push(signal);
        
        bdpo.logSuccess(`🚀 RESISTANCE BREAKOUT at ${resistance.price.toFixed(4)}`);
        bdpo.logInfo(`Level strength: ${resistance.strength.toFixed(2)}, Touches: ${resistance.touches.length}`);
        
        return signal;
      }
      
      if (this.showRejections && high > resistance.price * (1 - this.levelTolerance) && 
          close < resistance.price * (1 - this.levelTolerance)) {
        const signal = {
          type: 'resistance_rejection',
          price: resistance.price,
          bar: currentBar,
          level: resistance.price,
          strength: resistance.strength
        };
        this._signals.push(signal);
        
        bdpo.logWarn(`⬇️ RESISTANCE REJECTION at ${resistance.price.toFixed(4)}`);
        
        return signal;
      }
    }

    for (let support of this._supportLevels) {
      if (this.showBreakouts && low < support.price && close < support.price * (1 - this.levelTolerance)) {
        const signal = {
          type: 'support_breakdown',
          price: support.price,
          bar: currentBar,
          level: support.price,
          strength: support.strength
        };
        this._signals.push(signal);
        
        bdpo.logError(`📉 SUPPORT BREAKDOWN at ${support.price.toFixed(4)}`);
        bdpo.logInfo(`Level strength: ${support.strength.toFixed(2)}, Touches: ${support.touches.length}`);
        
        return signal;
      }
      
      if (this.showRejections && low < support.price * (1 + this.levelTolerance) && 
          close > support.price * (1 + this.levelTolerance)) {
        const signal = {
          type: 'support_bounce',
          price: support.price,
          bar: currentBar,
          level: support.price,
          strength: support.strength
        };
        this._signals.push(signal);
        
        bdpo.logSuccess(`⬆️ SUPPORT BOUNCE at ${support.price.toFixed(4)}`);
        
        return signal;
      }
    }

    return null;
  }

  private plotLevels(): void {
    const dataLength = bdpo.getClose().length;

    // Plot resistance levels
    this._resistanceLevels.forEach((level, index) => {
      const levelArray = new Array(dataLength).fill(level.price);
      const opacity = Math.min(0.8, 0.3 + (level.strength / 10));
      const lineWidth = Math.min(3, 1 + (level.touches.length / 2));

      bdpo.plot("splines", [levelArray], {
        color: "#F44336",
        width: lineWidth,
        lineStyle: level.touches.length >= 5 ? "solid" : "dashed",
        opacity: opacity
      }, {
        title: `Resistance ${level.price.toFixed(4)} (${level.touches.length} touches)`
      });
    });

    // Plot support levels
    this._supportLevels.forEach((level, index) => {
      const levelArray = new Array(dataLength).fill(level.price);
      const opacity = Math.min(0.8, 0.3 + (level.strength / 10));
      const lineWidth = Math.min(3, 1 + (level.touches.length / 2));

      bdpo.plot("splines", [levelArray], {
        color: "#4CAF50",
        width: lineWidth,
        lineStyle: level.touches.length >= 5 ? "solid" : "dashed",
        opacity: opacity
      }, {
        title: `Support ${level.price.toFixed(4)} (${level.touches.length} touches)`
      });
    });
  }

  private plotSignal(signal: any): void {
    const signalArray = new Array(bdpo.getClose().length).fill(null);
    signalArray[signal.bar] = signal.price;

    let color = "#FF9800";
    let shape = "circle";

    if (signal.type === 'resistance_breakout' || signal.type === 'support_bounce') {
      color = "#4CAF50";
      shape = "triangle";
    } else if (signal.type === 'resistance_rejection' || signal.type === 'support_breakdown') {
      color = "#F44336";
      shape = "triangle-down";
    }

    bdpo.plot("trades", signalArray.map((sig, i) => 
      sig ? [signal.type.includes('breakout') || signal.type.includes('bounce') ? 1 : -1, sig, signal.type.toUpperCase()] : null
    ).filter(Boolean), {
      color: color,
      size: 8,
      shape: shape
    }, {
      title: signal.type.replace('_', ' ').toUpperCase()
    });
  }

  __shutdown__(): void {
    bdpo.logInfo(`Support/Resistance shutdown - ${this._supportLevels.length} support, ${this._resistanceLevels.length} resistance levels`);
    bdpo.logInfo(`Total signals generated: ${this._signals.length}`);
  }
}
Key Features:
  • Automatic Level Detection: Uses pivot point analysis to find significant levels
  • Strength Calculation: Levels get stronger with more touches and higher pivot significance
  • Breakout Detection: Identifies when price breaks through established levels
  • Rejection Signals: Detects when price respects support/resistance levels
  • Adaptive Display: Line thickness and opacity indicate level strength

Trend Analysis Indicators

Multi-Timeframe MACD
MACD analysis across multiple timeframes for comprehensive trend analysis

Indicator Overview

This indicator combines MACD signals from multiple timeframes to provide a more robust trend analysis, helping identify high-probability trade setups when multiple timeframes align.

class MultiTimeframeMacd {
  // Public configuration
  public fastPeriod: number = 12;           // Fast EMA period
  public slowPeriod: number = 26;           // Slow EMA period
  public signalPeriod: number = 9;          // Signal line period
  public timeframes: number[] = [1, 4, 15, 60]; // Timeframe multipliers
  public minAlignment: number = 3;          // Minimum timeframes for signal
  public showDivergence: boolean = true;    // Show divergence analysis
  public divergenceLookback: number = 20;   // Bars to look back for divergence
  public alertOnAlignment: boolean = true;  // Alert when timeframes align

  // Private state
  private _timeframeData: Map = new Map();
  private _alignmentHistory: Array<{bar: number, aligned: number, direction: string}> = [];
  private _divergenceSignals: Array<{bar: number, type: string, timeframe: number}> = [];

  __init__(): void {
    this._timeframeData.clear();
    this._alignmentHistory = [];
    this._divergenceSignals = [];
    
    // Initialize data structures for each timeframe
    this.timeframes.forEach(tf => {
      this._timeframeData.set(tf, {
        closes: [],
        macdLine: [],
        signalLine: [],
        histogram: [],
        trend: 'neutral',
        strength: 0,
        lastCrossover: null
      });
    });
    
    bdpo.logInfo("Multi-Timeframe MACD initialized");
    bdpo.logInfo(`Timeframes: ${this.timeframes.join(', ')}, Min Alignment: ${this.minAlignment}`);
  }

  __tick__(): any {
    const closes = bdpo.getClose();
    const highs = bdpo.getHigh();
    const lows = bdpo.getLow();
    const currentBar = closes.length - 1;

    if (currentBar < Math.max(...this.timeframes) * this.slowPeriod + 10) {
      return { status: "warming_up" };
    }

    // Update MACD for each timeframe
    this.updateTimeframeData(closes, currentBar);
    
    // Analyze alignment across timeframes
    const alignment = this.analyzeAlignment(currentBar);
    
    // Check for divergences
    const divergence = this.checkDivergence(closes, highs, lows, currentBar);
    
    // Plot multi-timeframe MACD
    this.plotMultiTimeframeMacd();
    
    // Generate alerts if needed
    if (alignment.aligned >= this.minAlignment && this.alertOnAlignment) {
      this.generateAlignmentAlert(alignment);
    }

    return {
      alignment: alignment,
      divergence: divergence,
      timeframeSignals: this.getTimeframeSignals(),
      overallTrend: this.getOverallTrend(),
      strength: this.calculateOverallStrength()
    };
  }

  private updateTimeframeData(closes: number[], currentBar: number): void {
    this.timeframes.forEach(tf => {
      const tfData = this._timeframeData.get(tf);
      
      // Get timeframe-adjusted data
      const tfCloses = this.getTimeframeData(closes, tf);
      
      if (tfCloses.length < this.slowPeriod + this.signalPeriod) {
        return;
      }

      // Calculate MACD for this timeframe
      const macd = bdpo.macd(tfCloses, this.fastPeriod, this.slowPeriod, this.signalPeriod);
      
      tfData.closes = tfCloses;
      tfData.macdLine = macd.macd;
      tfData.signalLine = macd.signal;
      tfData.histogram = macd.histogram;
      
      // Determine trend and strength
      const latestMacd = macd.macd[macd.macd.length - 1];
      const latestSignal = macd.signal[macd.signal.length - 1];
      const latestHist = macd.histogram[macd.histogram.length - 1];
      
      tfData.trend = latestMacd > latestSignal ? 'bullish' : 'bearish';
      tfData.strength = Math.abs(latestHist);
      
      // Check for crossovers
      if (macd.histogram.length >= 2) {
        const prevHist = macd.histogram[macd.histogram.length - 2];
        
        if (prevHist <= 0 && latestHist > 0) {
          tfData.lastCrossover = { type: 'bullish', bar: currentBar };
        } else if (prevHist >= 0 && latestHist < 0) {
          tfData.lastCrossover = { type: 'bearish', bar: currentBar };
        }
      }
    });
  }

  private getTimeframeData(data: number[], multiplier: number): number[] {
    if (multiplier === 1) return data;
    
    const tfData: number[] = [];
    for (let i = multiplier - 1; i < data.length; i += multiplier) {
      // Use the close of each period
      tfData.push(data[i]);
    }
    
    return tfData;
  }

  private analyzeAlignment(currentBar: number): any {
    let bullishCount = 0;
    let bearishCount = 0;
    let totalStrength = 0;
    const timeframeStates: any[] = [];

    this.timeframes.forEach(tf => {
      const tfData = this._timeframeData.get(tf);
      
      if (tfData && tfData.trend) {
        timeframeStates.push({
          timeframe: tf,
          trend: tfData.trend,
          strength: tfData.strength,
          crossover: tfData.lastCrossover
        });
        
        if (tfData.trend === 'bullish') {
          bullishCount++;
        } else if (tfData.trend === 'bearish') {
          bearishCount++;
        }
        
        totalStrength += tfData.strength;
      }
    });

    const alignment = {
      bar: currentBar,
      aligned: Math.max(bullishCount, bearishCount),
      direction: bullishCount > bearishCount ? 'bullish' : 
                bearishCount > bullishCount ? 'bearish' : 'neutral',
      bullishTimeframes: bullishCount,
      bearishTimeframes: bearishCount,
      totalStrength: totalStrength,
      timeframeStates: timeframeStates
    };

    this._alignmentHistory.push(alignment);
    
    // Keep only recent history
    if (this._alignmentHistory.length > 100) {
      this._alignmentHistory.shift();
    }

    return alignment;
  }

  private checkDivergence(closes: number[], highs: number[], lows: number[], currentBar: number): any {
    if (!this.showDivergence || currentBar < this.divergenceLookback) {
      return null;
    }

    // Check divergence on the primary timeframe (first in array)
    const primaryTf = this.timeframes[0];
    const tfData = this._timeframeData.get(primaryTf);
    
    if (!tfData || tfData.macdLine.length < this.divergenceLookback) {
      return null;
    }

    // Find recent highs and lows
    const startBar = currentBar - this.divergenceLookback;
    const priceHigh = Math.max(...highs.slice(startBar, currentBar + 1));
    const priceLow = Math.min(...lows.slice(startBar, currentBar + 1));
    
    const macdHigh = Math.max(...tfData.macdLine.slice(-this.divergenceLookback));
    const macdLow = Math.min(...tfData.macdLine.slice(-this.divergenceLookback));
    
    const currentPrice = closes[currentBar];
    const currentMacd = tfData.macdLine[tfData.macdLine.length - 1];
    
    // Check for bullish divergence (price makes lower low, MACD makes higher low)
    if (currentPrice <= priceLow * 1.001 && currentMacd > macdLow * 1.1) {
      const divergence = {
        type: 'bullish_divergence',
        bar: currentBar,
        timeframe: primaryTf,
        strength: (currentMacd - macdLow) / Math.abs(macdLow)
      };
      
      this._divergenceSignals.push(divergence);
      
      bdpo.logSuccess(`📈 BULLISH DIVERGENCE detected on ${primaryTf}x timeframe`);
      bdpo.logInfo(`Price: ${currentPrice.toFixed(4)}, MACD: ${currentMacd.toFixed(6)}`);
      
      return divergence;
    }
    
    // Check for bearish divergence (price makes higher high, MACD makes lower high)
    if (currentPrice >= priceHigh * 0.999 && currentMacd < macdHigh * 0.9) {
      const divergence = {
        type: 'bearish_divergence',
        bar: currentBar,
        timeframe: primaryTf,
        strength: (macdHigh - currentMacd) / Math.abs(macdHigh)
      };
      
      this._divergenceSignals.push(divergence);
      
      bdpo.logWarn(`📉 BEARISH DIVERGENCE detected on ${primaryTf}x timeframe`);
      bdpo.logInfo(`Price: ${currentPrice.toFixed(4)}, MACD: ${currentMacd.toFixed(6)}`);
      
      return divergence;
    }

    return null;
  }

  private generateAlignmentAlert(alignment: any): void {
    if (alignment.aligned >= this.minAlignment) {
      const strength = alignment.totalStrength > 0.001 ? 'Strong' : 'Moderate';
      
      if (alignment.direction === 'bullish') {
        bdpo.logSuccess(`🟢 ${strength} BULLISH ALIGNMENT`);
      } else if (alignment.direction === 'bearish') {
        bdpo.logError(`🔴 ${strength} BEARISH ALIGNMENT`);
      }
      
      bdpo.logInfo(`${alignment.aligned}/${this.timeframes.length} timeframes aligned ${alignment.direction}`);
      bdpo.logInfo(`Total strength: ${alignment.totalStrength.toFixed(6)}`);
    }
  }

  private getTimeframeSignals(): any {
    const signals: any = {};
    
    this.timeframes.forEach(tf => {
      const tfData = this._timeframeData.get(tf);
      if (tfData) {
        signals[`tf_${tf}`] = {
          trend: tfData.trend,
          strength: tfData.strength,
          lastCrossover: tfData.lastCrossover
        };
      }
    });
    
    return signals;
  }

  private getOverallTrend(): string {
    if (this._alignmentHistory.length === 0) return 'neutral';
    
    const recent = this._alignmentHistory[this._alignmentHistory.length - 1];
    return recent.direction;
  }

  private calculateOverallStrength(): number {
    if (this._alignmentHistory.length === 0) return 0;
    
    const recent = this._alignmentHistory[this._alignmentHistory.length - 1];
    return recent.totalStrength / this.timeframes.length;
  }

  private plotMultiTimeframeMacd(): void {
    // Plot MACD for each timeframe in separate panels
    this.timeframes.forEach((tf, index) => {
      const tfData = this._timeframeData.get(tf);
      
      if (tfData && tfData.macdLine.length > 0) {
        const panelName = `macd_tf_${tf}`;
        
        // Extend data to match main chart length
        const dataLength = bdpo.getClose().length;
        const extendedMacd = this.extendTimeframeData(tfData.macdLine, tf, dataLength);
        const extendedSignal = this.extendTimeframeData(tfData.signalLine, tf, dataLength);
        const extendedHist = this.extendTimeframeData(tfData.histogram, tf, dataLength);
        
        // Plot MACD line
        bdpo.plot("splines", [extendedMacd], {
          color: "#2196F3",
          width: 2
        }, {
          title: `MACD ${tf}x`,
          panel: panelName
        });
        
        // Plot Signal line
        bdpo.plot("splines", [extendedSignal], {
          color: "#FF9800",
          width: 1
        }, {
          title: `Signal ${tf}x`,
          panel: panelName
        });
        
        // Plot Histogram
        bdpo.plot("histogram", [extendedHist], {
          color: tfData.trend === 'bullish' ? "#4CAF50" : "#F44336",
          opacity: 0.6
        }, {
          title: `Histogram ${tf}x`,
          panel: panelName
        });
      }
    });

    // Plot alignment strength
    const alignmentStrengths = this._alignmentHistory.map(a => a.totalStrength);
    if (alignmentStrengths.length > 0) {
      const extendedStrengths = new Array(bdpo.getClose().length).fill(null);
      const startIndex = Math.max(0, extendedStrengths.length - alignmentStrengths.length);
      
      for (let i = 0; i < alignmentStrengths.length; i++) {
        extendedStrengths[startIndex + i] = alignmentStrengths[i];
      }
      
      bdpo.plot("splines", [extendedStrengths], {
        color: "#9C27B0",
        width: 2
      }, {
        title: "Alignment Strength",
        panel: "alignment_panel"
      });
    }

    // Plot divergence signals
    this._divergenceSignals.forEach(signal => {
      const signalArray = new Array(bdpo.getClose().length).fill(null);
      signalArray[signal.bar] = signal.type === 'bullish_divergence' ? 1 : -1;
      
      bdpo.plot("trades", signalArray.map((sig, i) => 
        sig ? [sig, bdpo.getClose()[i], `${signal.type.toUpperCase()}_${signal.timeframe}x`] : null
      ).filter(Boolean), {
        color: signal.type === 'bullish_divergence' ? "#4CAF50" : "#F44336",
        size: 8,
        shape: signal.type === 'bullish_divergence' ? "triangle" : "triangle-down"
      }, {
        title: `Divergence ${signal.timeframe}x`
      });
    });
  }

  private extendTimeframeData(tfData: number[], multiplier: number, targetLength: number): number[] {
    const extended = new Array(targetLength).fill(null);
    
    for (let i = 0; i < tfData.length; i++) {
      const targetIndex = (i + 1) * multiplier - 1;
      if (targetIndex < targetLength) {
        // Fill the range for this timeframe period
        for (let j = Math.max(0, targetIndex - multiplier + 1); j <= targetIndex && j < targetLength; j++) {
          extended[j] = tfData[i];
        }
      }
    }
    
    return extended;
  }

  __shutdown__(): void {
    const finalAlignment = this._alignmentHistory[this._alignmentHistory.length - 1];
    bdpo.logInfo(`Multi-Timeframe MACD shutdown`);
    bdpo.logInfo(`Final alignment: ${finalAlignment?.aligned || 0}/${this.timeframes.length} timeframes`);
    bdpo.logInfo(`Total divergence signals: ${this._divergenceSignals.length}`);
  }
}
Important Implementation Notes:
  • Timeframe Data: This example uses simple period sampling - real MTF analysis may require actual timeframe data
  • Performance: Multiple timeframe calculations can be computationally intensive
  • Data Synchronization: Ensure proper alignment between different timeframe datasets
  • Signal Filtering: Use alignment requirements to filter false signals
Custom Ichimoku Cloud
Enhanced Ichimoku Kinko Hyo with signal analysis and cloud strength calculation

Indicator Overview

This enhanced Ichimoku indicator provides traditional Ichimoku components plus additional analysis features like cloud strength, momentum signals, and trend confirmation across multiple timeframes.

class CustomIchimokuCloud {
  // Public configuration
  public tenkanPeriod: number = 9;          // Tenkan-sen (Conversion Line) period
  public kijunPeriod: number = 26;          // Kijun-sen (Base Line) period  
  public senkouBPeriod: number = 52;        // Senkou Span B period
  public displacement: number = 26;         // Cloud displacement
  public showSignals: boolean = true;       // Show entry/exit signals
  public showCloudStrength: boolean = true; // Show cloud strength analysis
  public strongCloudThreshold: number = 0.005; // Strong cloud threshold (0.5%)
  public signalConfirmation: boolean = true; // Require multiple confirmations
  public alertOnSignals: boolean = true;    // Generate alerts for signals

  // Private state
  private _tenkanSen: number[] = [];
  private _kijunSen: number[] = [];
  private _senkouSpanA: number[] = [];
  private _senkouSpanB: number[] = [];
  private _chikouSpan: number[] = [];
  private _cloudStrength: number[] = [];
  private _trendDirection: string[] = [];
  private _signals: Array<{type: string, bar: number, price: number, strength: number}> = [];
  private _lastSignalBar: number = -1;

  __init__(): void {
    this._tenkanSen = [];
    this._kijunSen = [];
    this._senkouSpanA = [];
    this._senkouSpanB = [];
    this._chikouSpan = [];
    this._cloudStrength = [];
    this._trendDirection = [];
    this._signals = [];
    this._lastSignalBar = -1;
    
    bdpo.logInfo("Custom Ichimoku Cloud initialized");
    bdpo.logInfo(`Periods - Tenkan: ${this.tenkanPeriod}, Kijun: ${this.kijunPeriod}, Senkou B: ${this.senkouBPeriod}`);
  }

  __tick__(): any {
    const closes = bdpo.getClose();
    const highs = bdpo.getHigh();
    const lows = bdpo.getLow();
    const currentBar = closes.length - 1;

    if (currentBar < this.senkouBPeriod + this.displacement + 5) {
      return { status: "warming_up" };
    }

    // Calculate Ichimoku components
    this.calculateIchimokuLines(highs, lows, closes, currentBar);
    
    // Analyze cloud strength and trend
    this.analyzeCloudStrength(currentBar);
    
    // Check for signals
    const signal = this.checkForSignals(closes, highs, lows, currentBar);
    
    // Plot Ichimoku components
    this.plotIchimoku();
    
    if (signal) {
      this.plotSignal(signal);
    }

    return {
      tenkanSen: this._tenkanSen[this._tenkanSen.length - 1],
      kijunSen: this._kijunSen[this._kijunSen.length - 1],
      senkouSpanA: this._senkouSpanA[this._senkouSpanA.length - 1],
      senkouSpanB: this._senkouSpanB[this._senkouSpanB.length - 1],
      chikouSpan: this._chikouSpan[this._chikouSpan.length - 1],
      cloudStrength: this._cloudStrength[this._cloudStrength.length - 1],
      trendDirection: this._trendDirection[this._trendDirection.length - 1],
      aboveCloud: this.isAboveCloud(closes[currentBar], currentBar),
      signal: signal,
      totalSignals: this._signals.length
    };
  }

  private calculateIchimokuLines(highs: number[], lows: number[], closes: number[], currentBar: number): void {
    // Calculate Tenkan-sen (Conversion Line)
    this._tenkanSen = [];
    for (let i = this.tenkanPeriod - 1; i < highs.length; i++) {
      const periodHigh = Math.max(...highs.slice(i - this.tenkanPeriod + 1, i + 1));
      const periodLow = Math.min(...lows.slice(i - this.tenkanPeriod + 1, i + 1));
      this._tenkanSen.push((periodHigh + periodLow) / 2);
    }

    // Calculate Kijun-sen (Base Line)
    this._kijunSen = [];
    for (let i = this.kijunPeriod - 1; i < highs.length; i++) {
      const periodHigh = Math.max(...highs.slice(i - this.kijunPeriod + 1, i + 1));
      const periodLow = Math.min(...lows.slice(i - this.kijunPeriod + 1, i + 1));
      this._kijunSen.push((periodHigh + periodLow) / 2);
    }

    // Calculate Senkou Span A (Leading Span A)
    this._senkouSpanA = [];
    const minLength = Math.min(this._tenkanSen.length, this._kijunSen.length);
    for (let i = 0; i < minLength; i++) {
      this._senkouSpanA.push((this._tenkanSen[i] + this._kijunSen[i]) / 2);
    }

    // Calculate Senkou Span B (Leading Span B)
    this._senkouSpanB = [];
    for (let i = this.senkouBPeriod - 1; i < highs.length; i++) {
      const periodHigh = Math.max(...highs.slice(i - this.senkouBPeriod + 1, i + 1));
      const periodLow = Math.min(...lows.slice(i - this.senkouBPeriod + 1, i + 1));
      this._senkouSpanB.push((periodHigh + periodLow) / 2);
    }

    // Calculate Chikou Span (Lagging Span) - just the close displaced backwards
    this._chikouSpan = [...closes];
  }

  private analyzeCloudStrength(currentBar: number): void {
    this._cloudStrength = [];
    this._trendDirection = [];

    for (let i = 0; i < Math.min(this._senkouSpanA.length, this._senkouSpanB.length); i++) {
      const spanA = this._senkouSpanA[i];
      const spanB = this._senkouSpanB[i];
      
      // Calculate cloud thickness as percentage
      const thickness = Math.abs(spanA - spanB);
      const midPrice = (spanA + spanB) / 2;
      const strengthPct = midPrice > 0 ? (thickness / midPrice) : 0;
      
      this._cloudStrength.push(strengthPct);
      
      // Determine trend direction based on cloud
      if (spanA > spanB) {
        this._trendDirection.push(strengthPct > this.strongCloudThreshold ? 'strong_bullish' : 'weak_bullish');
      } else if (spanA < spanB) {
        this._trendDirection.push(strengthPct > this.strongCloudThreshold ? 'strong_bearish' : 'weak_bearish');
      } else {
        this._trendDirection.push('neutral');
      }
    }
  }

  private checkForSignals(closes: number[], highs: number[], lows: number[], currentBar: number): any {
    if (!this.showSignals || currentBar <= this._lastSignalBar + 3) {
      return null; // Prevent signal spam
    }

    const currentPrice = closes[currentBar];
    const tenkanIdx = currentBar - (closes.length - this._tenkanSen.length);
    const kijunIdx = currentBar - (closes.length - this._kijunSen.length);
    
    if (tenkanIdx < 1 || kijunIdx < 1) return null;

    const currentTenkan = this._tenkanSen[tenkanIdx];
    const prevTenkan = this._tenkanSen[tenkanIdx - 1];
    const currentKijun = this._kijunSen[kijunIdx];
    const prevKijun = this._kijunSen[kijunIdx - 1];

    // Check for Tenkan-Kijun crossover
    let signal = null;

    // Bullish signal: Tenkan crosses above Kijun
    if (prevTenkan <= prevKijun && currentTenkan > currentKijun) {
      signal = {
        type: 'tenkan_kijun_bullish',
        bar: currentBar,
        price: currentPrice,
        strength: this.calculateSignalStrength(currentBar, 'bullish'),
        tenkan: currentTenkan,
        kijun: currentKijun
      };
    }
    // Bearish signal: Tenkan crosses below Kijun
    else if (prevTenkan >= prevKijun && currentTenkan < currentKijun) {
      signal = {
        type: 'tenkan_kijun_bearish',
        bar: currentBar,
        price: currentPrice,
        strength: this.calculateSignalStrength(currentBar, 'bearish'),
        tenkan: currentTenkan,
        kijun: currentKijun
      };
    }

    // Additional confirmation checks
    if (signal && this.signalConfirmation) {
      const cloudPosition = this.isAboveCloud(currentPrice, currentBar);
      const chikouConfirmation = this.checkChikouConfirmation(closes, currentBar);
      
      signal.cloudPosition = cloudPosition;
      signal.chikouConfirmation = chikouConfirmation;
      
      // Only proceed with confirmed signals
      if ((signal.type.includes('bullish') && cloudPosition === 'above' && chikouConfirmation) ||
          (signal.type.includes('bearish') && cloudPosition === 'below' && chikouConfirmation)) {
        signal.confirmed = true;
      } else {
        signal.confirmed = false;
        signal = null; // Filter out unconfirmed signals
      }
    }

    if (signal) {
      this._signals.push(signal);
      this._lastSignalBar = currentBar;
      
      if (this.alertOnSignals) {
        this.generateSignalAlert(signal);
      }
    }

    return signal;
  }

  private calculateSignalStrength(currentBar: number, direction: string): number {
    let strength = 1;
    
    // Add cloud strength to signal strength
    if (this._cloudStrength.length > 0) {
      const cloudIdx = Math.min(currentBar, this._cloudStrength.length - 1);
      strength += this._cloudStrength[cloudIdx] * 100;
    }
    
    // Add trend direction confirmation
    if (this._trendDirection.length > 0) {
      const trendIdx = Math.min(currentBar, this._trendDirection.length - 1);
      const trend = this._trendDirection[trendIdx];
      
      if ((direction === 'bullish' && trend.includes('bullish')) ||
          (direction === 'bearish' && trend.includes('bearish'))) {
        strength *= trend.includes('strong') ? 2 : 1.5;
      }
    }
    
    return Math.min(10, strength); // Cap at 10
  }

  private isAboveCloud(price: number, bar: number): string {
    // Get cloud values at current position (accounting for displacement)
    const spanAIdx = Math.max(0, bar - this.displacement);
    const spanBIdx = Math.max(0, bar - this.displacement);
    
    if (spanAIdx >= this._senkouSpanA.length || spanBIdx >= this._senkouSpanB.length) {
      return 'unknown';
    }
    
    const spanA = this._senkouSpanA[spanAIdx];
    const spanB = this._senkouSpanB[spanBIdx];
    const cloudTop = Math.max(spanA, spanB);
    const cloudBottom = Math.min(spanA, spanB);
    
    if (price > cloudTop) return 'above';
    if (price < cloudBottom) return 'below';
    return 'inside';
  }

  private checkChikouConfirmation(closes: number[], currentBar: number): boolean {
    // Check if Chikou Span is clear of price action
    const chikouBar = currentBar - this.displacement;
    if (chikouBar < 0 || chikouBar >= closes.length) return false;
    
    const chikouPrice = closes[currentBar]; // Current close displaced back
    const historicalPrice = closes[chikouBar];
    
    // Simple confirmation: Chikou should be above/below historical price in trend direction
    return Math.abs(chikouPrice - historicalPrice) > historicalPrice * 0.001; // 0.1% minimum difference
  }

  private generateSignalAlert(signal: any): void {
    const direction = signal.type.includes('bullish') ? 'BULLISH' : 'BEARISH';
    const emoji = signal.type.includes('bullish') ? '🟢' : '🔴';
    
    if (signal.confirmed) {
      bdpo.logSuccess(`${emoji} CONFIRMED ${direction} ICHIMOKU SIGNAL`);
    } else {
      bdpo.logInfo(`${emoji} ${direction} ICHIMOKU SIGNAL (Unconfirmed)`);
    }
    
    bdpo.logInfo(`Tenkan: ${signal.tenkan?.toFixed(4)}, Kijun: ${signal.kijun?.toFixed(4)}`);
    bdpo.logInfo(`Strength: ${signal.strength?.toFixed(2)}, Cloud Position: ${signal.cloudPosition}`);
  }

  private plotIchimoku(): void {
    const dataLength = bdpo.getClose().length;

    // Extend arrays to match data length
    const extendedTenkan = this.extendArray(this._tenkanSen, dataLength);
    const extendedKijun = this.extendArray(this._kijunSen, dataLength);
    const extendedSpanA = this.extendArrayWithDisplacement(this._senkouSpanA, dataLength, this.displacement);
    const extendedSpanB = this.extendArrayWithDisplacement(this._senkouSpanB, dataLength, this.displacement);
    const extendedChikou = this.extendArrayWithDisplacement(this._chikouSpan, dataLength, -this.displacement);

    // Plot Tenkan-sen (Conversion Line)
    bdpo.plot("splines", [extendedTenkan], {
      color: "#FF6B6B",
      width: 1,
      lineStyle: "solid"
    }, {
      title: `Tenkan-sen (${this.tenkanPeriod})`
    });

    // Plot Kijun-sen (Base Line)
    bdpo.plot("splines", [extendedKijun], {
      color: "#4ECDC4",
      width: 2,
      lineStyle: "solid"
    }, {
      title: `Kijun-sen (${this.kijunPeriod})`
    });

    // Plot Senkou Span A (Leading Span A)
    bdpo.plot("splines", [extendedSpanA], {
      color: "#95E1D3",
      width: 1,
      lineStyle: "solid"
    }, {
      title: "Senkou Span A"
    });

    // Plot Senkou Span B (Leading Span B)
    bdpo.plot("splines", [extendedSpanB], {
      color: "#FFD93D",
      width: 1,
      lineStyle: "solid"
    }, {
      title: "Senkou Span B"
    });

    // Plot Chikou Span (Lagging Span)
    bdpo.plot("splines", [extendedChikou], {
      color: "#A8E6CF",
      width: 1,
      lineStyle: "dashed"
    }, {
      title: "Chikou Span"
    });

    // Plot cloud strength if enabled
    if (this.showCloudStrength && this._cloudStrength.length > 0) {
      const extendedStrength = this.extendArray(this._cloudStrength, dataLength);
      const strengthPercent = extendedStrength.map(s => s ? s * 100 : null);
      
      bdpo.plot("splines", [strengthPercent], {
        color: "#9C27B0",
        width: 2
      }, {
        title: "Cloud Strength %",
        panel: "ichimoku_strength"
      });
    }
  }

  private plotSignal(signal: any): void {
    const signalArray = new Array(bdpo.getClose().length).fill(null);
    signalArray[signal.bar] = signal.price;

    const color = signal.type.includes('bullish') ? "#4CAF50" : "#F44336";
    const shape = signal.confirmed ? "triangle" : "circle";

    bdpo.plot("trades", signalArray.map((sig, i) => 
      sig ? [signal.type.includes('bullish') ? 1 : -1, sig, signal.type.toUpperCase()] : null
    ).filter(Boolean), {
      color: color,
      size: signal.confirmed ? 10 : 6,
      shape: shape
    }, {
      title: `Ichimoku ${signal.type.replace('_', ' ')}`
    });
  }

  private extendArray(arr: number[], targetLength: number): number[] {
    const extended = new Array(targetLength).fill(null);
    const startIndex = targetLength - arr.length;
    
    for (let i = 0; i < arr.length; i++) {
      if (startIndex + i >= 0) {
        extended[startIndex + i] = arr[i];
      }
    }
    
    return extended;
  }

  private extendArrayWithDisplacement(arr: number[], targetLength: number, displacement: number): number[] {
    const extended = new Array(targetLength).fill(null);
    
    for (let i = 0; i < arr.length; i++) {
      const targetIndex = i + displacement + (targetLength - arr.length);
      if (targetIndex >= 0 && targetIndex < targetLength) {
        extended[targetIndex] = arr[i];
      }
    }
    
    return extended;
  }

  __shutdown__(): void {
    const confirmedSignals = this._signals.filter(s => s.confirmed).length;
    bdpo.logInfo(`Custom Ichimoku shutdown - Total signals: ${this._signals.length}, Confirmed: ${confirmedSignals}`);
    
    if (this._trendDirection.length > 0) {
      const currentTrend = this._trendDirection[this._trendDirection.length - 1];
      bdpo.logInfo(`Final trend: ${currentTrend}`);
    }
  }
}
Enhanced Features:
  • Traditional Components: All standard Ichimoku lines with proper displacement
  • Cloud Strength Analysis: Quantifies cloud thickness for trend strength assessment
  • Signal Confirmation: Multiple confirmation criteria for higher quality signals
  • Trend Classification: Categorizes trends as strong/weak bullish/bearish
  • Visual Enhancement: Color-coded components with strength indicators
Important Notes:
  • Analysis Only: Indicators provide market analysis and cannot place trades
  • Visual Overlays: Use plotting functions to display indicators on charts
  • Parameter Tuning: Adjust parameters based on your analysis timeframe and market
  • Performance: Complex indicators may require optimization for real-time use
  • Data Requirements: Ensure sufficient historical data for accurate calculations