agentsclimarketplace

Powerlanguage python conversion

Skill KasperChenGH/multicharts-powerlanguage/skills/powerlanguage-python-conversion

Use when converting or porting code between MultiCharts PowerLanguage and Python in either direction — Strategy ABC scaffold, concept mapping tables, semantic differences, pandas-ta/TA-Lib indicators, conversion checklists.From its SKILL.md

Install
npx -y skills add KasperChenGH/multicharts-powerlanguage --skill powerlanguage-python-conversion

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 9 stars9 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

37.8 KB, as published. Nobody here has run it

PowerLanguage ↔ Python Conversion

How to use

This skill covers the structural and semantic differences between MultiCharts PowerLanguage and Python for algorithmic trading. It does not duplicate the syntax rules of PowerLanguage — for declarations, bar references, control flow, and built-in function signatures use the powerlanguage-syntax skill. When performing a conversion in either direction, work through Part 0 (structural template) to set up the boilerplate, then Part 1 (concept mapping) for line-by-line translation, then review Part 2 (semantic differences and gotchas), and finally run through the relevant checklist in Part 3 before calling the output complete.


Part 0: Structural Template

PowerLanguage runs the entire script top-to-bottom on each bar close automatically. Python has no implicit bar loop, no built-in OHLCV type, and no order submission mechanism. Every PL-to-Python conversion targets the framework-agnostic scaffold below.

Bar dataclass

from dataclasses import dataclass

@dataclass
class Bar:
    open: float
    high: float
    low: float
    close: float
    volume: float
    time: int          # Unix epoch seconds
    bar_number: int    # 1-based to match PL convention

Order types

from enum import Enum, auto
from dataclasses import dataclass

class Side(Enum):
    LONG = auto()
    SHORT = auto()

class OrderType(Enum):
    MARKET = auto()
    LIMIT = auto()
    STOP = auto()

class Action(Enum):
    ENTRY = auto()
    EXIT = auto()
    STOP_LOSS = auto()
    PROFIT_TARGET = auto()
    BREAK_EVEN = auto()

@dataclass
class Order:
    label: str
    action: Action
    side: Side
    order_type: OrderType
    price: float = 0.0   # 0.0 for Market
    qty: float = 1.0

Strategy ABC

from abc import ABC, abstractmethod

class Strategy(ABC):
    @abstractmethod
    def on_bar(self, bars: list[Bar], orders: list[Order]) -> None:
        ...

Strategy subclass skeleton

import pandas as pd
import pandas_ta as ta

class MyStrategy(Strategy):
    def __init__(self, length: int = 20, threshold: float = 0.5):
        # Inputs (PL: Inputs: Length(20), Threshold(0.5))
        self.length = length
        self.threshold = threshold

        # Variables (PL: Variables: my_var(0))
        self.my_var = 0.0

        # Position state (PL: MarketPosition, EntryPrice, etc.)
        self.position = 0           # -1, 0, +1
        self.entry_price = 0.0
        self.bars_since_entry = 0
        self.current_contracts = 0.0

    def on_bar(self, bars: list[Bar], orders: list[Order]) -> None:
        if len(bars) < self.length:
            return

        closes = pd.Series([b.close for b in bars])

        # pandas-ta: compute indicator on the full series
        sma = ta.sma(closes, length=self.length)
        current_sma = sma.iloc[-1]

        # ... strategy logic, push to orders list ...

Alternative: TA-Lib indicator pattern

import talib
import numpy as np

class MyStrategy(Strategy):
    def on_bar(self, bars: list[Bar], orders: list[Order]) -> None:
        closes = np.array([b.close for b in bars])

        # TA-Lib: batch array API (NumPy in, NumPy out)
        sma = talib.SMA(closes, timeperiod=self.length)
        current_sma = sma[-1]

pandas-ta is recommended as the primary library (pure Python, zero install friction, pip-installable). TA-Lib is the performance alternative (C-based, requires compiling the C library). The concept mapping tables in Part 1 show both.

Main loop

def main():
    bars: list[Bar] = load_bars("data.csv")  # user-provided
    strategy = MyStrategy(length=20, threshold=0.5)

    for i in range(len(bars)):
        orders: list[Order] = []
        strategy.on_bar(bars[: i + 1], orders)
        # execute orders against simulated book...

The slice bars[:i+1] gives the strategy access to full history; bars[-1] is the current bar. This mirrors PL's implicit bar-by-bar execution.


Part 1: Concept Mapping

Table 1 — Declarations

PowerLanguagePythonNotes
Inputs: Length(20)__init__ parameter with default: def __init__(self, length: int = 20)Stored as self.length; treat as read-only after construction
Variables: myVar(0)self.my_var = 0.0 in __init__Mutable instance attributes
Value1 .. Value99Named attribute (e.g., self.rsi_val)Must rename to meaningful identifiers; Python has no pre-declared variables
Condition1 .. Condition99Named attribute (e.g., self.is_oversold)Same: rename to typed bool attributes
Arrays: buf[10](0)self.buf = [0.0] * 11PL [10] means last index is 10 (11 elements); use a Python list
IntraBarPersist myVar(0)self.my_var = 0.0 (same as Variables)Python has no bar-close vs intra-bar distinction; all instance attributes persist

Table 2 — Data Access

PowerLanguagePythonNotes
Open, High, Low, Close, Volumebars[-1].open, .high, .low, .close, .volumebars[-1] for current bar
Close[1]bars[-2].closeLookback N: bars[-1 - n].close. Guard with len(bars) > n.
Close of Data2separate bars2: list[Bar] parameterPL Data2 is a second chart feed; pass a second list to on_bar()
Datedatetime.fromtimestamp(bars[-1].time, tz=timezone.utc)PL Date is YYYMMDD integer; Python uses datetime module
Timedt.hour * 100 + dt.minute (from datetime object)PL Time is HHMM integer
BarNumber / CurrentBarbars[-1].bar_number or len(bars)PL is 1-based

Table 3 — Technical Indicators (dual-library)

PowerLanguagepandas-ta (primary)TA-Lib (alternative)Notes
Average(Close, N)ta.sma(closes, length=N)talib.SMA(closes, timeperiod=N)Current value: .iloc[-1] / [-1]
XAverage(Close, N)ta.ema(closes, length=N)talib.EMA(closes, timeperiod=N)
RSI(Close, N)ta.rsi(closes, length=N)talib.RSI(closes, timeperiod=N)Returns 0–100
BollingerBand(Close, N, 2)ta.bbands(closes, length=N, std=2)talib.BBANDS(closes, timeperiod=N, nbdevup=2, nbdevdn=2)Returns upper/mid/lower
MACD(Close, 12, 26)ta.macd(closes, fast=12, slow=26, signal=9)talib.MACD(closes, fastperiod=12, slowperiod=26, signalperiod=9)Returns MACD/signal/histogram
Stochastic(...)ta.stoch(highs, lows, closes, k=14, d=3)talib.STOCH(highs, lows, closes, fastk_period=14, slowk_period=3, slowd_period=3)Returns slowK/slowD
ADX(N)ta.adx(highs, lows, closes, length=N)talib.ADX(highs, lows, closes, timeperiod=N)
CCI(N)ta.cci(highs, lows, closes, length=N)talib.CCI(highs, lows, closes, timeperiod=N)
AvgTrueRange(N)ta.atr(highs, lows, closes, length=N, mamode='sma') or rolling mean of ta.true_range(...)talib.SMA(talib.TRANGE(highs, lows, closes), timeperiod=N)PL AvgTrueRange is a SIMPLE average of TrueRange; pandas-ta/TA-Lib ATR default to Wilder smoothing — the default Wilder ATR matches PL SmoothedAverage(TrueRange, Len) instead
Momentum(Close, N)ta.mom(closes, length=N)talib.MOM(closes, timeperiod=N)
Highest(High, N)highs.rolling(N).max()talib.MAX(highs, timeperiod=N)
Lowest(Low, N)lows.rolling(N).min()talib.MIN(lows, timeperiod=N)
DMIPlus(N) / DMIMinus(N)ta.dm(highs, lows, length=N)talib.PLUS_DI(...) / talib.MINUS_DI(...)
KeltnerChannel(Close, N, mult)ta.kc(highs, lows, closes, length=N, scalar=mult)Manual: talib.EMA(closes, N) ± mult * talib.ATR(highs, lows, closes, N)
Parabolic(step)ta.psar(highs, lows, af0=step, max_af=0.2)talib.SAR(highs, lows, acceleration=step, maximum=0.2)
PercentR(N)ta.willr(highs, lows, closes, length=N) + 100talib.WILLR(highs, lows, closes, timeperiod=N) + 100PL PercentR is POSITIVE 0..100 (= Williams %R + 100); library WILLR returns −100..0 — add 100, and thresholds become 80/20 on the positive scale
RateOfChange(Close, N)ta.roc(closes, length=N)talib.ROC(closes, timeperiod=N)
Volatility(N)ta.true_range(highs, lows, closes).ewm(span=N).mean() (or ta.atr(..., mamode='ema'))talib.EMA(talib.TRANGE(highs, lows, closes), timeperiod=N)PL Volatility is a smoothed average of TrueRange weighted toward the most recent bar — NOT a stdev-based measure (StandardDev is stdev of closes; VolatilityStdDev is annualized stdev of log returns)
StandardDev(Close, N, 1)ta.stdev(closes, length=N)talib.STDDEV(closes, timeperiod=N, nbdev=1)
TrueRangeta.true_range(highs, lows, closes)talib.TRANGE(highs, lows, closes)Per-bar, no period
MoneyFlow(N)ta.mfi(highs, lows, closes, volumes, length=N)talib.MFI(highs, lows, closes, volumes, timeperiod=N)Requires HLCV
AccumDistta.ad(highs, lows, closes, volumes)talib.AD(highs, lows, closes, volumes)Cumulative, no period
LinearRegValue(Close, N, 0)ta.linreg(closes, length=N)talib.LINEARREG(closes, timeperiod=N)
LinearRegSlope(Close, N)ta.linreg(closes, length=N, slope=True)talib.LINEARREG_SLOPE(closes, timeperiod=N)
Summation(Close, N)closes.rolling(N).sum()talib.SUM(closes, timeperiod=N)
Cum(Volume)volumes.cumsum()np.cumsum(volumes)Running total
WAverage(Close, N)ta.wma(closes, length=N)talib.WMA(closes, timeperiod=N)
MidPoint(Close, N)ta.midpoint(closes, length=N)talib.MIDPOINT(closes, timeperiod=N)
TSI(Close, LongLen, ShortLen)ta.tsi(closes, fast=ShortLen, slow=LongLen)Manual: double-EMA of momentum / double-EMA of abs(momentum)pandas-ta fast=short, slow=long (reversed naming vs PL)
SwingHigh(Occur, Price, Strength, Length)Manual: scan for pivot highManual: scan for pivot highOccurrence is the FIRST PL arg; returns −1 when no swing found; Length must exceed Strength
SwingLow(Occur, Price, Strength, Length)Manual: scan for pivot lowManual: scan for pivot lowSame: Occurrence first, −1 when none found, Length > Strength
HighestBar(High, N)highs[-N:].idxmax() → compute bars agonp.argmax(highs[-N:])Returns bars ago
LowestBar(Low, N)lows[-N:].idxmin() → compute bars agonp.argmin(lows[-N:])Returns bars ago
CountIF(cond, N)cond_series.rolling(N).sum()Manual: sum(1 for ...)Count True values
Crosses Overprev_a <= prev_b and a > bSameNo built-in; store previous values
Crosses Underprev_a >= prev_b and a < bSameSame
AverageFC(Close, N)ta.sma(closes, length=N)talib.SMA(closes, timeperiod=N)FC = "fast calculation": AverageFC = SummationFC/Len (running-sum SMA), numerically identical to Average — use SMA
AdaptiveMovAvg(Close, N)ta.kama(closes, length=N)talib.KAMA(closes, timeperiod=N)Kaufman Adaptive Moving Average
UltimateOscillator(7,14,28)ta.uo(highs, lows, closes, fast=7, medium=14, slow=28)talib.ULTOSC(highs, lows, closes, timeperiod1=7, timeperiod2=14, timeperiod3=28)Requires HLC
ChaikinOsc(3, 10)ta.adosc(highs, lows, closes, volumes, fast=3, slow=10)talib.ADOSC(highs, lows, closes, volumes, fastperiod=3, slowperiod=10)Requires HLCV
PriceOscillator(Fast, Slow)ta.apo(closes, fast=Fast, slow=Slow)talib.APO(closes, fastperiod=Fast, slowperiod=Slow)Absolute Price Oscillator
DirMovement(N, ...)ta.dm(highs, lows, length=N) + ta.adx(...)talib.PLUS_DI(...) + talib.MINUS_DI(...) + talib.ADX(...)Multi-output: +DI, −DI, ADX
Extremes(High, Low, N, oHH, oLL, oHHBar, oLLBar)Manual: rolling max/min + argmax/argminManual: rolling max/min + argmax/argminReturns highest, lowest, and their bar offsets
TrueHighnp.maximum(highs, closes.shift(1))Samemax(High, Close[1])
TrueLownp.minimum(lows, closes.shift(1))Samemin(Low, Close[1])
Rangehighs - lowsSamePer-bar range; no period
NthHighest(N, Close, Len)Manual: closes.rolling(Len).apply(lambda x: np.sort(x)[-N])Manual: sort rolling windowNth largest value in window
NthLowest(N, Close, Len)Manual: closes.rolling(Len).apply(lambda x: np.sort(x)[N-1])Manual: sort rolling windowNth smallest value in window
NthHighestBar(N, Close, Len)Manual: find bar offset of Nth highestManual: find bar offset of Nth highestReturns bars ago of Nth highest
NthLowestBar(N, Close, Len)Manual: find bar offset of Nth lowestManual: find bar offset of Nth lowestReturns bars ago of Nth lowest
SwingHighBar(Occur, Price, Strength, Length)Manual: pivot detection with bar offsetManual: pivot detection with bar offsetBars since Nth swing high; same arg order as SwingHigh
SwingLowBar(Occur, Price, Strength, Length)Manual: pivot detection with bar offsetManual: pivot detection with bar offsetBars since Nth swing low
LinearRegAngle(Close, N)np.degrees(np.arctan(ta.linreg(closes, length=N, slope=True)))talib.LINEARREG_ANGLE(closes, timeperiod=N)Slope converted to degrees
Correlation(Close, Volume, N)ta.correlation(closes, volumes, length=N)talib.CORREL(closes, volumes, timeperiod=N)Pearson correlation
RSquared(Close, N)ta.correlation(closes, ..., length=N) ** 2talib.CORREL(closes, ..., timeperiod=N) ** 2Square of correlation
StdError(Close, N)Manual: fit np.polyfit(np.arange(N), closes[-N:], 1), then np.sqrt(((y - yhat) ** 2).sum() / (N - 2))Manual: same residual computation on the windowStandard error of the linear-regression residuals — NOT stdev/sqrt(N) (that is SE of the mean)
Median(Close, N)ta.median(closes, length=N)closes.rolling(N).median()Rolling median
ELDate(dt)Manual: (y - 1900) * 10000 + m * 100 + dSameEasyLanguage YYYMMDD date format
MinutesToTime(mins)Manual: (mins // 60) * 100 + mins % 60SameMinutes since midnight to HHMM
TimeToMinutes(hhmm)Manual: (hhmm // 100) * 60 + hhmm % 100SameHHMM to minutes since midnight
AvgPrice(opens + highs + lows + closes) / 4talib.AVGPRICE(opens, highs, lows, closes)Average of OHLC
MedianPrice(highs + lows) / 2talib.MEDPRICE(highs, lows)Median of HL
TypicalPrice(highs + lows + closes) / 3talib.TYPPRICE(highs, lows, closes)Typical price HLC
WeightedClose(highs + lows + 2 * closes) / 4talib.WCLPRICE(highs, lows, closes)Weighted close HLCC
MRO(cond, N, 1)Manual: find Nth True in cond_series[-N:]Manual: iterate lookbackMost Recent Occurrence; returns bars ago
IFF(cond, trueVal, falseVal)trueVal if cond else falseValSamePython ternary; or np.where(cond, trueVal, falseVal) for Series
TriAverage(Close, N)ta.trima(closes, length=N)talib.TRIMA(closes, timeperiod=N)Triangular MA
FastK(N)ta.stoch(highs, lows, closes, k=N, d=1, smooth_k=1)['STOCHk_N_1_1']talib.STOCHF(highs, lows, closes, fastk_period=N)Raw Fast %K
FastD(N)ta.stoch(highs, lows, closes, k=N, d=3, smooth_k=1)['STOCHd_N_3_1']talib.STOCHF(highs, lows, closes, fastk_period=N, fastd_period=3)Smoothed Fast %D — set smooth_k=1 so %D is the SMA(3) of RAW %K, not of slowed %K
SlowK(N)ta.stoch(highs, lows, closes, k=N, d=3)['STOCHk_N_3_3']talib.STOCH(highs, lows, closes, fastk_period=N, slowk_period=3)Slow %K
SlowD(N)ta.stoch(highs, lows, closes, k=N, d=3)['STOCHd_N_3_3']talib.STOCH(highs, lows, closes, fastk_period=N, slowk_period=3, slowd_period=3)Slow %D
FastKCustom(H, L, C, N)ta.stoch(H, L, C, k=N, d=1, smooth_k=1)['STOCHk_...']talib.STOCHF(H, L, C, fastk_period=N)Custom prices
FastDCustom(H, L, C, N)ta.stoch(H, L, C, k=N, d=3)['STOCHd_...']talib.STOCHF(H, L, C, fastk_period=N, fastd_period=3)Custom prices
SlowKCustom(H, L, C, N)ta.stoch(H, L, C, k=N, d=3)['STOCHk_...']talib.STOCH(H, L, C, fastk_period=N, slowk_period=3)Custom prices
SlowDCustom(H, L, C, N)ta.stoch(H, L, C, k=N, d=3)["STOCHd_N_3_3"]talib.STOCH(H, L, C, fastk_period=N)[2]Slow %D with custom prices
StochasticExp(H, L, C, N, S1, S2, ...)Manual: compute FastK then apply EMA smoothingManual: compute with talib.STOCHF then talib.EMAExponential smoothing variant
ADXR(N)ta.adx(highs, lows, closes, length=N) then (adx + adx.shift(N)) / 2talib.ADXR(highs, lows, closes, timeperiod=N)TA-Lib has direct ADXR
ADXCustom(H, L, C, N)ta.adx(H, L, C, length=N)talib.ADX(H, L, C, timeperiod=N)Pass custom price Series
DMI(N)ta.adx(highs, lows, closes, length=N)talib.ADX(highs, lows, closes, timeperiod=N)Wrapper; same as ADX
DMIPlusCustom(H, L, C, N)ta.dm(H, L, length=N)['DMP_N']talib.PLUS_DI(H, L, C, timeperiod=N)+DI with custom prices
DMIMinusCustom(H, L, C, N)ta.dm(H, L, length=N)['DMN_N']talib.MINUS_DI(H, L, C, timeperiod=N)−DI with custom prices
ParabolicCustom(step, limit)ta.psar(highs, lows, af0=step, max_af=limit)talib.SAR(highs, lows, acceleration=step, maximum=limit)Parabolic SAR with custom limit
TRIX(Close, N)ta.trix(closes, length=N)talib.TRIX(closes, timeperiod=N)Triple EMA ROC
MassIndex(SmoothLen, SumLen)ta.massi(highs, lows, fast=SmoothLen, slow=SumLen)Manual: EMA ratio sumMass Index
EaseOfMovementta.eom(highs, lows, closes, volumes)Manual: distance moved / box ratioRequires HLCV
SwingIndexManual: Wilder swing index formulaManual: sameNo library built-in
AccumSwingIndexManual: cumulative sum of SwingIndexManual: sameNo library built-in
Detrend(Close, N)Manual: closes - closes.rolling(N).mean().shift(N // 2 + 1)Manual: offset SMADetrended price
PercentChange(Close, N)closes.pct_change(N) * 100talib.ROC(closes, timeperiod=N)Percent change
UlcerIndex(Close, N)ta.ui(closes, length=N)Manual: RMS of drawdown pctDownside volatility
ParabolicSAR(step, limit, ...)ta.psar(highs, lows, af0=step, max_af=limit)talib.SAR(highs, lows, acceleration=step, maximum=limit)Multi-output; extract position from PSARl/PSARs columns
LinearReg(Close, N, TgtBar, ...)ta.linreg(closes, length=N) + manual slope/angletalib.LINEARREG(closes, timeperiod=N) + talib.LINEARREG_SLOPE/ANGLE/INTERCEPTMulti-output; combine four TA-Lib calls
TrueRangeCustom(H, L, C)ta.true_range(H, L, C)talib.TRANGE(H, L, C)Custom prices
VolatilityStdDev(NumDays)np.log(closes / closes.shift(1)).rolling(NumDays).std() * np.sqrt(252)Manual: annualized stdev of log returnsHistorical volatility
StandardDevAnnual(Close, N, DataType)ta.stdev(closes, length=N) * np.sqrt(252)talib.STDDEV(closes, timeperiod=N) * np.sqrt(252)Annualized stdev
HighestFC(Close, N)closes.rolling(N).max()talib.MAX(closes, timeperiod=N)Same as Highest
LowestFC(Close, N)closes.rolling(N).min()talib.MIN(closes, timeperiod=N)Same as Lowest
PivotHighVS(Inst, Price, LStr, RStr, Len)Manual: scan for pivot high with L/R strengthManual: sameAsymmetric left/right strength
PivotLowVS(Inst, Price, LStr, RStr, Len)Manual: scan for pivot low with L/R strengthManual: sameSame
PivotHighVSBar(Inst, Price, LStr, RStr, Len)Manual: bars ago of pivot highManual: sameReturns offset
PivotLowVSBar(Inst, Price, LStr, RStr, Len)Manual: bars ago of pivot lowManual: sameReturns offset
Divergence(P1, P2, Str, Len, HiLo)Manual: compare pivot highs/lows of two seriesManual: sameNo library built-in
TimeSeriesForecast(Close, N)ta.tsf(closes, length=N)talib.TSF(closes, timeperiod=N)Time Series Forecast
SummationFC(Close, N)closes.rolling(N).sum()talib.SUM(closes, timeperiod=N)Same as Summation
OpenD(N)df.resample('D').first()['open'].iloc[-1-N]Manual: resample to dailyDaily open
HighD(N)df.resample('D').max()['high'].iloc[-1-N]Manual: resampleDaily high
LowD(N)df.resample('D').min()['low'].iloc[-1-N]Manual: resampleDaily low
CloseD(N)df.resample('D').last()['close'].iloc[-1-N]Manual: resampleDaily close
OpenW(N)df.resample('W').first()['open'].iloc[-1-N]Manual: resampleWeekly open
HighW(N)df.resample('W').max()['high'].iloc[-1-N]Manual: resampleWeekly high
LowW(N)df.resample('W').min()['low'].iloc[-1-N]Manual: resampleWeekly low
CloseW(N)df.resample('W').last()['close'].iloc[-1-N]Manual: resampleWeekly close
OpenM(N)df.resample('ME').first()['open'].iloc[-1-N]Manual: resampleMonthly open
HighM(N)df.resample('ME').max()['high'].iloc[-1-N]Manual: resampleMonthly high
LowM(N)df.resample('ME').min()['low'].iloc[-1-N]Manual: resampleMonthly low
CloseM(N)df.resample('ME').last()['close'].iloc[-1-N]Manual: resampleMonthly close
OpenY(N)df.resample('YE').first()['open'].iloc[-1-N]Manual: resampleYearly open
HighY(N)df.resample('YE').max()['high'].iloc[-1-N]Manual: resampleYearly high
LowY(N)df.resample('YE').min()['low'].iloc[-1-N]Manual: resampleYearly low
CloseY(N)df.resample('YE').last()['close'].iloc[-1-N]Manual: resampleYearly close
LRO(cond, N, Inst)Manual: cond_series[-N:].iloc[::-1] find Nth True from endManual: sameLeast Recent Occurrence
SummationIf(cond, Price, N)(Price * cond).rolling(N).sum()Manual: multiply then sumConditional rolling sum
IFFString(cond, trueStr, falseStr)trueStr if cond else falseStrSameString ternary; or np.where
OBVta.obv(closes, volumes)talib.OBV(closes, volumes)On Balance Volume
VolumeROC(N)ta.roc(volumes, length=N)talib.ROC(volumes, timeperiod=N)Volume rate of change
VolumeOsc(ShortLen, LongLen)ta.sma(volumes, length=ShortLen) - ta.sma(volumes, length=LongLen)talib.SMA(volumes, ShortLen) - talib.SMA(volumes, LongLen)Volume oscillator
PriceVolTrendta.pvt(closes, volumes)Manual: cumulative pct_change * volumePrice Volume Trend
LWAccDisManual: ((closes - opens) / (highs - lows) * volumes).cumsum()Manual: sameLarry Williams A/D
Fisher(Price)Manual: normalize, then 0.5 * np.log((1 + norm) / (1 - norm))Manual: sameFisher transformation
FisherINV(Price)Manual: (np.exp(2 * Price) - 1) / (np.exp(2 * Price) + 1)Manual: sameInverse Fisher
C_Doji(Pct)Manual: abs(close - open) <= (high - low) * Pct / 100talib.CDLDOJI(opens, highs, lows, closes)TA-Lib has CDL* family
C_Hammer_HangingMan(Len, Factor, ...)Manual: body/shadow ratiostalib.CDLHAMMER(...) / talib.CDLHANGINGMAN(...)Separate TA-Lib functions
C_BullEng_BearEng(Len, ...)Manual: engulfing detectiontalib.CDLENGULFING(opens, highs, lows, closes)+100=bullish, -100=bearish
C_BullHar_BearHar(Len, ...)Manual: harami detectiontalib.CDLHARAMI(opens, highs, lows, closes)+100=bullish, -100=bearish
C_MornDoji_EveDoji(Len, Pct, ...)Manual: 3-bar doji startalib.CDLMORNINGDOJISTAR(...) / talib.CDLEVENINGDOJISTAR(...)Separate functions
C_MornStar_EveStar(Len, ...)Manual: 3-bar startalib.CDLMORNINGSTAR(...) / talib.CDLEVENINGSTAR(...)Separate functions
C_PierceLine_DkCloud(Len, ...)Manual: piercing/cloudtalib.CDLPIERCING(...) / talib.CDLDARKCLOUDCOVER(...)Separate functions
C_ShootingStar(Len, Factor)Manual: shooting startalib.CDLSHOOTINGSTAR(opens, highs, lows, closes)TA-Lib direct
C_3WhSolds_3BlkCrows(Len, Factor, ...)Manual: 3-bar trendtalib.CDL3WHITESOLDIERS(...) / talib.CDL3BLACKCROWS(...)Separate functions
Statistical extended
AvgDeviation(Close, N)ta.mad(closes, length=N)Manual: (closes - closes.rolling(N).mean()).abs().rolling(N).mean()Mean absolute deviation
Variance(Close, N)ta.variance(closes, length=N)talib.VAR(closes, timeperiod=N)Population variance
Kurtosis(Close, N)closes.rolling(N).kurt()Manual: 4th momentExcess kurtosis
Skew(Close, N)closes.rolling(N).skew()Manual: 3rd momentSkewness
PercentRank(ValToRank, Price, N)closes.rolling(N).apply(lambda w: stats.percentileofscore(w, w.iloc[-1]))ManualPercent rank
Covariance(P1, P2, N)P1.rolling(N).cov(P2, ddof=0)ManualCovariance — pandas defaults to SAMPLE covariance (ddof=1); pass ddof=0 for the population form PL uses
Quartile(Close, N, Q)closes.rolling(N).quantile(Q*0.25)ManualQuartile value
TrimMean(Close, N, Pct)closes.rolling(N).apply(lambda w: stats.trim_mean(w, Pct/100))ManualTrimmed mean
Mode(Close, N, Type)closes.rolling(N).apply(lambda w: w.mode().iloc[0])ManualModal value
HarmonicMean(Close, N)closes.rolling(N).apply(stats.hmean)ManualHarmonic mean
Moving averages extended
SmoothedAverage(Close, N)ta.rma(closes, length=N)Manual: Wilder smoothingWilder/RMA
Miscellaneous
BarAnnualizationManual: compute from bar frequencyManualBars-per-year factor
LastBarOnChartidx == len(df) - 1ManualTrue on last bar
Custom functions
StochRSI(Close, N, M)pandas_ta.stochrsi(df["close"], length=N, rsi_length=N, k=M)pandas_ta.stochrsi()Stochastic RSI
supertrend(N, Mult)pandas_ta.supertrend(df["high"], df["low"], df["close"], length=N, multiplier=Mult)pandas_ta.supertrend()Supertrend
NVI(Start)Manual: accumulate on volume-down barsManualNegative Volume Index
PVI(Start)Manual: accumulate on volume-up barsManualPositive Volume Index
Coppo(N1, N2, N3)pandas_ta.coppock(df["close"], length=N3, fast=N2, slow=N1)pandas_ta.coppock()Coppock Curve
LWTI(Close, P, N)Manual: (sma(close-close.shift(P), N) / sma(high-low, N)) * 50 + 50ManualLarry Williams TI
TVI(Close, Vol, Tick)Manual: cumulative directional volumeManualTrade Volume Index
SharpeRatio(Period, Rate, Calc, Cap)Manual: (returns.mean() - rf) / returns.std()ManualPortfolio Sharpe
WRSI(N, Close)pandas_ta.rsi(df["close"], length=N)pandas_ta.rsi()Wilder RSI (default)
NewMA(Close, N)Manual: Heikin-Ashi + TEMA hybridManualHybrid MA

Table 4 — Strategy Orders

PowerLanguagePythonNotes
Buy("label") next bar marketorders.append(Order(label="label", action=Action.ENTRY, side=Side.LONG, order_type=OrderType.MARKET))Append to orders list
SellShort("label") next bar marketOrder(action=Action.ENTRY, side=Side.SHORT, order_type=OrderType.MARKET)Side.SHORT for short entry
Sell("label") next bar marketClose long: Order(action=Action.EXIT, side=Side.LONG, order_type=OrderType.MARKET)WARNING: PL Sell exits a long — do NOT map to Side.SHORT
BuyToCover("label") next bar marketClose short: Order(action=Action.EXIT, side=Side.SHORT, order_type=OrderType.MARKET)PL BuyToCover exits an existing short
Buy("label") next bar at price limitOrder(order_type=OrderType.LIMIT, price=price)Limit order
Buy("label") next bar at price stopOrder(order_type=OrderType.STOP, price=price)Stop order
SetStopLoss(dollars)stop_price = entry_price - dollars / (qty * point_value)PL takes a dollar amount; Python must convert to price level
SetProfitTarget(dollars)target_price = entry_price + dollars / (qty * point_value)Same dollar-to-price conversion

Table 5 — Plotting

PowerLanguagePythonNotes
Plot1(value, "label")print(f"{label}: {value}") or log to fileNo chart in Python; output to stdout, logging module, or results list
SetPlotColor(1, Red)N/ANo visual output; skip or store as metadata
SetPlotWidth(1, 2)N/ASkip
NoPlot(1)Skip output for that barConditional: don't append to results

Table 6 — Control Flow

PowerLanguagePythonNotes
If cond Then Begin ... End;if cond:Direct mapping
If cond Then ... Else ...if cond: ... else: ...Direct mapping
For idx = 1 to n Begin ... End;for i in range(1, n + 1):PL For is inclusive; Python range excludes upper bound. PL side uses idx because i is reserved (alias for OpenInterest)
For idx = n DownTo 1 Begin ... End;for i in range(n, 0, -1):PL DownTo; Python reversed range
While cond Begin ... End;while cond:Direct mapping
Switch (expr) Begin Case 1: ... End;match expr: with case 1: ...Python 3.10+ match/case; no fallthrough (matches PL); PL empty case body is a compile error — use Value1 = Value1; as no-op
Once Begin ... End;if not self._once_done: ... self._once_done = TrueUse a bool attribute; PL Once runs on first bar only

Table 7 — Other Built-ins and Features

PowerLanguagePythonNotes
MarketPositionself.position (int: −1/0/+1)Track manually; update on order fills
EntryPriceself.entry_price (float)Track manually; set when position opens
CurrentContractsself.current_contracts (float)Track manually; absolute contract count
BarsSinceEntryself.bars_since_entry (int)Increment each bar while position != 0; reset on new entry
Print("text")print("text")Direct mapping
#BeginCmtry ... #EndCmtryNo equivalentPL expert commentary; skip or log
Alert("msg")print("ALERT: msg", file=sys.stderr)No built-in alert; write to stderr or use callback

Part 2: Semantic Differences and Gotchas

Order fill timing — the #1 conversion bug

PL Buy next bar at market fills at the NEXT bar's OPEN, and next bar at X Stop / Limit fills intrabar on the next bar at the stop/limit price (or at the open if the bar gaps through it). A converted simulator that flips position on the signal bar (same-bar close fill) is one bar early and uses the wrong price.

The executor must queue orders produced on bar N and fill them against bar N+1:

for i in range(len(bars) - 1):
    orders: list[Order] = []
    strategy.on_bar(bars[: i + 1], orders)        # orders queued on bar i...
    nxt = bars[i + 1]
    for o in orders:                              # ...filled on bar i+1
        if o.order_type is OrderType.MARKET:
            fill(o, price=nxt.open)                          # next bar's OPEN
        elif o.order_type is OrderType.STOP and nxt.high >= o.price:  # buy stop
            fill(o, price=max(o.price, nxt.open))            # intrabar at the stop price
        # sell stop: nxt.low <= o.price; limit orders mirror with reversed comparisons

EMA seeding/warmup gotcha: PL XAverage seeds recursively from the first bar's price. pandas-ta ema defaults to an SMA seed (sma=True) — pass sma=False to match PL, or document the difference. TA-Lib TA_EMA seeds with the SMA of the first Length values. Early-history signals therefore differ across targets — discard roughly the first 3×Length bars before comparing backtests.

  1. Sell does not mean "go short". In PowerLanguage, Sell exits an existing long position. The Python equivalent is closing the position (setting self.position = 0). Do not create a new short entry as a translation of Sell.

  2. PL is implicitly bar-driven; Python requires an explicit loop. PowerLanguage executes the entire script top-to-bottom on each bar close automatically. In Python, you must write the for i in range(len(bars)) loop yourself and call on_bar() on each iteration. Forgetting the loop wrapper means the strategy logic runs only once.

  3. Negative indexing is convenient but still needs bounds guarding. bars[-2].close is Pythonic for "previous bar" but raises IndexError when len(bars) < 2. Guard with if len(bars) > n before any lookback access. PL handles this automatically via MaxBarsBack; Python does not.

  4. pandas-ta operates on full Series, not streaming. Each on_bar() call recomputes the indicator on the entire series up to the current bar. This is correct but O(n²) over a backtest. For performance, pre-compute indicators outside the loop or cache results.

  5. NaN values in indicator output. pandas-ta returns NaN for the first length - 1 bars where the indicator has insufficient history. Always check pd.notna(value) or not math.isnan(value) before using the result. PL silently returns 0 for insufficient history; Python raises errors or produces silent NaN propagation.

  6. Mutable default arguments are a classic Python trap. Never write def __init__(self, buf=[]) — use None and assign inside: self.buf = buf if buf is not None else []. This doesn't affect PL conversion directly, but catches new Python users.

  7. Python match does not fall through. Unlike C++ switch, Python 3.10+ match/case has no fallthrough. This matches PL's Switch/Case behavior, so the mapping is direct.

  8. Float equality. Use math.isclose(a, b) or abs(a - b) < 1e-10 instead of ==. PL uses tolerance-based comparison for = on float values; Python == on floats is exact bitwise comparison.

  9. No Value1..Value99 or Condition1..Condition99. These PL pre-declared variables must be replaced with named instance attributes. Use descriptive identifiers like self.rsi_value and self.is_oversold.

  10. Position state is not tracked automatically. PL provides MarketPosition, EntryPrice, CurrentContracts, and BarsSinceEntry as built-in read-only variables updated by the engine. In Python, you must maintain these as instance attributes and update them manually whenever the execution engine fills an order.

  11. Dollar amounts versus price levels for stops. SetStopLoss and SetProfitTarget in PL accept a dollar (currency) amount. Python has no implicit conversion — you must compute the price level manually: stop_price = entry_price - stop_dollars / (qty * point_value).

  12. Multi-data series requires explicit design. PL Close of Data2 accesses a second feed bound to the chart. In Python, you must pass a second bar list (bars2: list[Bar]) to on_bar() or store it as an attribute. There is no implicit secondary-feed mechanism.

  13. No Portfolio Money Management (PMM) equivalent. PL's PMM allows a single script to govern position sizing across multiple instruments. Python has no built-in portfolio-level abstraction. You must implement cross-instrument logic as a separate orchestrator that calls individual strategy instances.

  14. datetime.utcfromtimestamp is deprecated in Python 3.12+. Use datetime.fromtimestamp(ts, tz=timezone.utc) instead to avoid deprecation warnings.


Part 3: Conversion Checklists

PL → Python Pre-Conversion

  • List every Inputs: declaration → __init__ parameter with default
  • List every Variables: declaration → instance attribute in __init__
  • Identify Value1..99 / Condition1..99 → plan meaningful replacement names
  • Identify all indicator calls → confirm pandas-ta / TA-Lib equivalent exists
  • Locate Data2/Data3 references → plan multi-feed architecture
  • Locate SetStopLoss/SetProfitTarget → note point value for dollar→price conversion
  • Check IntraBarPersist → decide if tick-level behavior matters
  • Identify .elf function calls → plan Python function equivalents

PL → Python Post-Conversion

  • Every Sell maps to closing the long, not entering short
  • Every BuyToCover maps to closing the short, not entering long
  • All bar lookback accesses guarded with len(bars) > n
  • All indicator results checked for NaN before use
  • Crosses Over / Crosses Under implemented as two-bar comparisons
  • MarketPosition, EntryPrice, CurrentContracts, BarsSinceEntry tracked as instance attributes
  • Dollar-based stop/target amounts converted to price levels
  • Value1..99 and Condition1..99 renamed to descriptive typed attributes
  • Code runs with python -Wall script.py with zero errors and zero warnings
  • Strategy back-tested on same instrument/date range; trade count and P&L in same order of magnitude

Python → PL Pre-Conversion

  • List all __init__ attributes → categorize: inputs (immutable) vs variables (mutable) vs indicator state
  • Identify all pandas-ta / TA-Lib calls → map each to PL built-in
  • Check for match statements with guard clauses → plan PL Switch or If/Else
  • Identify any libraries beyond pandas-ta/TA-Lib → plan PL equivalents
  • Check for explicit position-tracking attributes → confirm PL built-ins suffice

Python → PL Post-Conversion

  • Every short entry maps to SellShort, not Sell
  • Every long close maps to Sell, not SellShort
  • All bars[-1-n].close lookbacks converted to Close[n]
  • All manual crossover comparisons converted to PL Crosses Over / Crosses Under
  • All attributes categorized: immutable → Inputs:, mutable → Variables:
  • All pandas-ta/TA-Lib calls replaced with PL function calls
  • NaN handling removed (PL handles lookback alignment internally)
  • Price-level stops/targets converted to dollar amounts for SetStopLoss/SetProfitTarget
  • Script compiled in MultiCharts PowerEditor with zero errors

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.