Powerlanguage cpp conversion
Skill KasperChenGH/multicharts-powerlanguage/skills/powerlanguage-cpp-conversion
Use when converting or porting code between MultiCharts PowerLanguage and C++ in either direction — Strategy base-class scaffold, concept mapping tables, semantic differences, TA-Lib indicators, conversion checklists.From its SKILL.md
npx -y skills add KasperChenGH/multicharts-powerlanguage --skill powerlanguage-cpp-conversionAssembled 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
38.3 KB, as published. Nobody here has run it
PowerLanguage ↔ C++ Conversion
How to use
This skill covers the structural and semantic differences between MultiCharts PowerLanguage and C++ 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. C++ has no implicit bar loop, no built-in OHLCV type, and no order submission mechanism. Every PL-to-C++ conversion targets the framework-agnostic scaffold below.
Bar struct
#include <cstdint>
#include <string>
#include <vector>
struct Bar {
double open;
double high;
double low;
double close;
double volume;
int64_t time; // Unix epoch seconds
size_t bar_number; // 1-based to match PL convention
};
Order types
enum class Side { Long, Short };
enum class OrderType { Market, Limit, Stop };
struct Order {
std::string label;
Side side;
OrderType type;
double price; // 0.0 for Market
double qty;
};
Strategy base class
class Strategy {
public:
virtual ~Strategy() = default;
virtual void on_bar(const std::vector<Bar>& bars,
std::vector<Order>& orders) = 0;
};
Strategy subclass skeleton
#include "ta_libc.h" // TA-Lib
class MyStrategy : public Strategy {
public:
// Inputs (PL: Inputs: Length(20))
MyStrategy(int length, double threshold)
: length_(length), threshold_(threshold) {}
void on_bar(const std::vector<Bar>& bars,
std::vector<Order>& orders) override;
private:
// Inputs (immutable after construction)
int length_;
double threshold_;
// Variables (PL: Variables: my_var(0))
double my_var_ = 0.0;
// Position state (PL: MarketPosition, EntryPrice, etc.)
int position_ = 0; // -1, 0, +1
double entry_price_ = 0.0;
int bars_since_entry_ = 0;
double current_contracts_ = 0.0;
};
TA-Lib call pattern
// Extract close prices into a contiguous array (TA-Lib needs parallel arrays)
std::vector<double> closes(bars.size());
for (size_t i = 0; i < bars.size(); ++i) closes[i] = bars[i].close;
// Batch API: compute SMA over the full close array
int outBeg = 0, outNBElement = 0;
std::vector<double> sma_out(bars.size());
TA_SMA(0, static_cast<int>(closes.size()) - 1,
closes.data(), length_,
&outBeg, &outNBElement, sma_out.data());
// Current bar's SMA is the LAST valid output element
double current_sma = sma_out[outNBElement - 1];
The output array is NOT aligned with the input array. The first valid output is at sma_out[0], which corresponds to closes[outBeg]. The current value is always sma_out[outNBElement - 1].
Main loop
int main() {
std::vector<Bar> bars = load_bars("data.csv"); // user-provided
MyStrategy strategy(20, 0.5);
std::vector<Order> orders;
for (size_t i = 0; i < bars.size(); ++i) {
orders.clear();
std::vector<Bar> history(bars.begin(), bars.begin() + i + 1);
strategy.on_bar(history, orders);
// execute orders against simulated book...
}
return 0;
}
The history vector up to and including bar i mirrors PL's implicit bar-by-bar execution. bars.back() inside on_bar() is the current bar.
Incremental alternative (TA-Lib RT)
TA-Lib RT is a community fork that adds streaming APIs: TA_SMA_StateInit(), TA_SMA_State(), TA_SMA_StateFree(). These maintain internal state across calls, avoiding full-array recomputation on each bar. Use TA-Lib RT when performance matters for large bar counts.
Part 1: Concept Mapping
Table 1 — Declarations
| PowerLanguage | C++ | Notes |
|---|---|---|
Inputs: Length(20) | int length_ member + constructor param | Inputs become const or private members set at construction |
Variables: myVar(0) | double my_var_ = 0.0 member | Mutable members; initialize in-class or in the constructor initializer list |
Value1 .. Value99 | named member (e.g., double rsi_val_ = 0.0) | Must rename to meaningful identifiers |
Condition1 .. Condition99 | named member (e.g., bool is_oversold_ = false) | Same: rename to typed bool members |
Arrays: buf[10](0) | double buf_[11] = {}; or std::vector<double> buf_(11, 0.0) | PL [10] means last index is 10 (11 elements) |
IntraBarPersist myVar(0) | double my_var_ = 0.0 member (same as Variables) | C++ has no bar-close vs intra-bar distinction; all members persist |
Table 2 — Data Access
| PowerLanguage | C++ | Notes |
|---|---|---|
Open, High, Low, Close, Volume | bars.back().open, .high, .low, .close, .volume | bars.back() for current bar |
Close[1] | bars[bars.size() - 2].close | Lookback N: bars[bars.size() - 1 - n].close. Check bars.size() > n to avoid UB. |
Close of Data2 | separate const std::vector<Bar>& bars2 parameter | PL Data2 is a second chart feed; pass a second vector reference |
Date | time_t t = bar.time; auto* tm = std::gmtime(&t); → tm->tm_year, tm->tm_mon, tm->tm_mday | PL Date is YYYMMDD integer (YYY = years since 1900); C++ uses <ctime> or <chrono>. Cast int64_t to time_t for portability. |
Time | time_t t = bar.time; auto* tm = std::gmtime(&t); → tm->tm_hour, tm->tm_min | PL Time is HHMM integer |
BarNumber / CurrentBar | bar.bar_number or i + 1 (loop index) | PL is 1-based |
Table 3 — Technical Indicators
| PowerLanguage | C++ (TA-Lib) | Notes |
|---|---|---|
Average(Close, Length) | TA_SMA(0, endIdx, inClose, length, &outBeg, &outNB, outSma) | Current value: outSma[outNB - 1] |
XAverage(Close, Length) | TA_EMA(0, endIdx, inClose, length, &outBeg, &outNB, outEma) | Same batch pattern |
RSI(Close, Length) | TA_RSI(0, endIdx, inClose, length, &outBeg, &outNB, outRsi) | Returns 0–100 range |
Stochastic(...) | TA_STOCH(0, endIdx, inHigh, inLow, inClose, fastK, slowK, slowKMAType, slowD, slowDMAType, &outBeg, &outNB, outSlowK, outSlowD) | Full %K/%D smoothing control — maps better to PL's 11-param version than Pine's simple ta.stoch |
ADX(Length) | TA_ADX(0, endIdx, inHigh, inLow, inClose, length, &outBeg, &outNB, outAdx) | Direct equivalent |
CCI(Length) | TA_CCI(0, endIdx, inHigh, inLow, inClose, length, &outBeg, &outNB, outCci) | PL CCI takes only length (uses HLC internally); TA-Lib requires explicit HLC arrays |
AvgTrueRange(Length) | TA_TRANGE(0, endIdx, inHigh, inLow, inClose, &outBeg, &outNB, outTr) then TA_SMA over outTr | PL AvgTrueRange is a SIMPLE average of TrueRange. TA_ATR is Wilder-smoothed — NOT equivalent; TA_ATR instead matches PL SmoothedAverage(TrueRange, Len) |
BollingerBand(Close, Length, 2) | TA_BBANDS(0, endIdx, inClose, length, 2.0, 2.0, TA_MAType_SMA, &outBeg, &outNB, outUpper, outMiddle, outLower) | Returns three arrays: upper, middle, lower |
Close Crosses Over MA | prev_close <= prev_ma && close > ma | No built-in crossover in TA-Lib; implement as two-bar comparison |
Close Crosses Under MA | prev_close >= prev_ma && close < ma | Same pattern |
Highest(Close, Length) | TA_MAX(0, endIdx, inClose, length, &outBeg, &outNB, outMax) | Direct equivalent |
Lowest(Close, Length) | TA_MIN(0, endIdx, inClose, length, &outBeg, &outNB, outMin) | Direct equivalent |
Momentum(Close, Length) | TA_MOM(0, endIdx, inClose, length, &outBeg, &outNB, outMom) | Direct equivalent |
TSI(Close, LongLen, ShortLen) | Manual: compute momentum, apply TA_EMA twice (long then short) to both momentum and abs(momentum), then 100.0 * smoothed_mtm / smoothed_abs_mtm | TA-Lib has no TA_TSI; chain two EMA passes |
AverageFC(Close, Length) | TA_SMA(0, endIdx, inClose, length, &outBeg, &outNB, outSma) | FC = "fast calculation": AverageFC = SummationFC/Len (running-sum SMA), numerically identical to Average; TA_SMA gives the same values |
WAverage(Close, Length) | TA_WMA(0, endIdx, inClose, length, &outBeg, &outNB, outWma) | Weighted moving average; direct equivalent |
AdaptiveMovAvg(Close, Length) | TA_KAMA(0, endIdx, inClose, length, &outBeg, &outNB, outKama) | Kaufman Adaptive MA; direct equivalent |
MidPoint(Close, Length) | TA_MIDPOINT(0, endIdx, inClose, length, &outBeg, &outNB, outMid) | (Highest + Lowest) / 2 over period |
MACD(Close, FastLen, SlowLen) | TA_MACD(0, endIdx, inClose, fastLen, slowLen, signalLen, &outBeg, &outNB, outMACD, outSignal, outHist) | Returns three arrays: MACD line, signal, histogram |
KeltnerChannel(Close, Length, Mult) | Manual: TA_EMA for midline ± Mult * TA_ATR for bands | No single TA-Lib function; combine EMA + ATR |
DMIPlus(Length) | TA_PLUS_DI(0, endIdx, inHigh, inLow, inClose, length, &outBeg, &outNB, outPlusDI) | +DI component of DMI |
DMIMinus(Length) | TA_MINUS_DI(0, endIdx, inHigh, inLow, inClose, length, &outBeg, &outNB, outMinusDI) | −DI component of DMI |
RateOfChange(Close, Length) | TA_ROC(0, endIdx, inClose, length, &outBeg, &outNB, outRoc) | Percentage rate of change |
PercentR(Length) | TA_WILLR(0, endIdx, inHigh, inLow, inClose, length, &outBeg, &outNB, outWillR) then add 100 to each value | PL PercentR is POSITIVE 0..100 (= Williams %R + 100); TA_WILLR returns −100..0 — thresholds become 80/20 on the positive scale |
MoneyFlow(Length) | TA_MFI(0, endIdx, inHigh, inLow, inClose, inVolume, length, &outBeg, &outNB, outMfi) | Money Flow Index; requires HLCV arrays |
Parabolic(AFStep, AFMax) | TA_SAR(0, endIdx, inHigh, inLow, AFStep, AFMax, &outBeg, &outNB, outSar) | Parabolic SAR; requires HL arrays |
Volatility(Length) | TA_TRANGE(0, endIdx, inHigh, inLow, inClose, &outBeg, &outNB, outTr) then TA_EMA over outTr (approximation) | PL Volatility is an EMA-weighted average of TrueRange (weighted toward the most recent bar) — NOT a stdev of log returns (that is VolatilityStdDev) |
UltimateOscillator(Len1, Len2, Len3) | TA_ULTOSC(0, endIdx, inHigh, inLow, inClose, len1, len2, len3, &outBeg, &outNB, outUltOsc) | Three-period weighted oscillator |
ChaikinOsc(FastLen, SlowLen) | TA_ADOSC(0, endIdx, inHigh, inLow, inClose, inVolume, fastLen, slowLen, &outBeg, &outNB, outAdosc) | Chaikin A/D Oscillator; requires HLCV |
PriceOscillator(FastLen, SlowLen) | TA_APO(0, endIdx, inClose, fastLen, slowLen, TA_MAType_SMA, &outBeg, &outNB, outApo) | Absolute Price Oscillator |
DirMovement(Length, oPlusDI, oMinusDI, oADX) | Call TA_PLUS_DI, TA_MINUS_DI, and TA_ADX separately | Multi-output; split into three TA-Lib calls |
Extremes(Length, oHighest, oLowest) | Manual: track running max/min over lookback window | No single TA-Lib function; use TA_MAX + TA_MIN or manual loop |
TrueRange | TA_TRANGE(0, endIdx, inHigh, inLow, inClose, &outBeg, &outNB, outTRange) | Single-bar true range; direct equivalent |
StandardDev(Close, Length) | TA_STDDEV(0, endIdx, inClose, length, 1.0, &outBeg, &outNB, outStdDev) | nbDev param = 1.0 for one standard deviation |
TrueHigh | std::max(bars[i].high, bars[i-1].close) | Manual one-liner; guard i > 0 |
TrueLow | std::min(bars[i].low, bars[i-1].close) | Manual one-liner; guard i > 0 |
Range | bars[i].high - bars[i].low | Simple arithmetic; no TA-Lib needed |
HighestBar(Close, Length) | TA_MAXINDEX(0, endIdx, inClose, length, &outBeg, &outNB, outIdx) | Returns index of highest value; convert to bars-ago offset |
LowestBar(Close, Length) | TA_MININDEX(0, endIdx, inClose, length, &outBeg, &outNB, outIdx) | Returns index of lowest value; convert to bars-ago offset |
NthHighest(N, Close, Length) | Manual: copy window into temp array, std::nth_element, pick Nth | No TA-Lib equivalent |
NthLowest(N, Close, Length) | Manual: copy window, std::nth_element, pick Nth from low end | No TA-Lib equivalent |
NthHighestBar(N, Close, Length) | Manual: find Nth highest value, then scan for its bar index | No TA-Lib equivalent |
NthLowestBar(N, Close, Length) | Manual: find Nth lowest value, then scan for its bar index | No TA-Lib equivalent |
SwingHigh(Occur, Price, Strength, Length) | Manual: candidate bar higher than Strength bars on each side, scanning the last Length bars | Occurrence is the FIRST PL arg (Nth most recent swing); returns −1 when none found; Length must exceed Strength; no TA-Lib equivalent |
SwingLow(Occur, Price, Strength, Length) | Manual: candidate bar lower than Strength bars on each side, scanning the last Length bars | Mirror of SwingHigh with reversed comparisons; −1 when none found |
SwingHighBar(Occur, Price, Strength, Length) | Manual: find SwingHigh, return bars-ago offset | Same pivot logic, return offset instead of price |
SwingLowBar(Occur, Price, Strength, Length) | Manual: find SwingLow, return bars-ago offset | Same pivot logic, return offset instead of price |
Summation(Close, Length) | TA_SUM(0, endIdx, inClose, length, &outBeg, &outNB, outSum) | Rolling sum; direct equivalent |
Cum(Close) | Manual: cumulative_ += bars.back().close (running total as member) | Cumulative sum from bar 1; no period param |
LinearRegValue(Close, Length, Offset) | TA_LINEARREG(0, endIdx, inClose, length, &outBeg, &outNB, outLinReg) | Offset projection requires manual adjustment |
LinearRegAngle(Close, Length) | TA_LINEARREG_ANGLE(0, endIdx, inClose, length, &outBeg, &outNB, outAngle) | Angle in degrees; direct equivalent |
LinearRegSlope(Close, Length) | TA_LINEARREG_SLOPE(0, endIdx, inClose, length, &outBeg, &outNB, outSlope) | Slope coefficient; direct equivalent |
Correlation(Close, Close2, Length) | TA_CORREL(0, endIdx, inClose, inClose2, length, &outBeg, &outNB, outCorr) | Pearson correlation; direct equivalent |
RSquared(Close, Length) | Manual: pow(TA_CORREL result, 2) | Coefficient of determination; square the correlation |
StdError(Close, Length) | Manual: compute residuals against the TA_LINEARREG fit, then sqrt(sum_sq_resid / (length - 2)) | Standard error of the linear-regression residuals — NOT TA_STDDEV / sqrt(length) (that is SE of the mean) |
Median(Close, Length) | Manual: copy window, std::nth_element(begin, mid, end), read middle | No TA-Lib rolling median |
ELDate | Manual: convert bar.time to YYYMMDD format (YYY = year − 1900) | PL EasyLanguage date format |
MinutesToTime(mins) | Manual: int hhmm = (mins / 60) * 100 + (mins % 60) | Convert total minutes to HHMM integer |
TimeToMinutes(hhmm) | Manual: int mins = (hhmm / 100) * 60 + (hhmm % 100) | Convert HHMM integer to total minutes |
AvgPrice | TA_AVGPRICE(0, endIdx, inOpen, inHigh, inLow, inClose, &outBeg, &outNB, outAvg) | (O+H+L+C)/4; direct equivalent |
MedianPrice | TA_MEDPRICE(0, endIdx, inHigh, inLow, &outBeg, &outNB, outMedPr) | (H+L)/2; direct equivalent |
TypicalPrice | TA_TYPPRICE(0, endIdx, inHigh, inLow, inClose, &outBeg, &outNB, outTyp) | (H+L+C)/3; direct equivalent |
WeightedClose | TA_WCLPRICE(0, endIdx, inHigh, inLow, inClose, &outBeg, &outNB, outWcl) | (H+L+2C)/4; direct equivalent |
CountIF(Cond, Length) | Manual: int count = 0; for (int j = 0; j < length; ++j) if (cond[size-1-j]) ++count; | Count true occurrences in lookback window |
MRO(Cond, Length, Instance) | Manual: scan backwards through lookback for Nth true occurrence, return bars-ago | Most Recent Occurrence; manual loop |
AccumDist | TA_AD(0, endIdx, inHigh, inLow, inClose, inVolume, &outBeg, &outNB, outAD) | Cumulative A/D line; requires HLCV arrays |
IFF(Cond, TrueVal, FalseVal) | cond ? trueVal : falseVal | C++ ternary operator; direct equivalent |
TriAverage(Close, Length) | TA_TRIMA(0, endIdx, inClose, length, &outBeg, &outNB, outTrima) | Triangular MA; direct equivalent |
FastK(StochLength) | TA_STOCHF(0, endIdx, inHigh, inLow, inClose, StochLength, 1, TA_MAType_SMA, &outBeg, &outNB, outFastK, outFastD) | Use TA_STOCHF for fast stochastic; outFastK is raw %K |
FastD(StochLength) | TA_STOCHF(0, endIdx, inHigh, inLow, inClose, StochLength, 3, TA_MAType_SMA, &outBeg, &outNB, outFastK, outFastD) | outFastD is smoothed Fast %D |
SlowK(StochLength) | TA_STOCH(0, endIdx, inHigh, inLow, inClose, StochLength, 3, TA_MAType_SMA, 3, TA_MAType_SMA, &outBeg, &outNB, outSlowK, outSlowD) | outSlowK from full stochastic |
SlowD(StochLength) | TA_STOCH(0, endIdx, inHigh, inLow, inClose, StochLength, 3, TA_MAType_SMA, 3, TA_MAType_SMA, &outBeg, &outNB, outSlowK, outSlowD) | outSlowD from full stochastic |
FastKCustom(H, L, C, StochLen) | TA_STOCHF(0, endIdx, H, L, C, StochLen, 1, TA_MAType_SMA, &outBeg, &outNB, outFastK, outFastD) | Custom price arrays |
FastDCustom(H, L, C, StochLen) | TA_STOCHF(0, endIdx, H, L, C, StochLen, 3, TA_MAType_SMA, &outBeg, &outNB, outFastK, outFastD) | Custom prices; use outFastD |
SlowKCustom(H, L, C, StochLen) | TA_STOCH(0, endIdx, H, L, C, StochLen, 3, TA_MAType_SMA, 3, TA_MAType_SMA, &outBeg, &outNB, outSlowK, outSlowD) | Custom prices |
SlowDCustom(H, L, C, StochLen) | TA_STOCH(0, endIdx, H, L, C, StochLen, 3, TA_MAType_SMA, 3, TA_MAType_SMA, &outBeg, &outNB, outSlowK, outSlowD) | Slow %D with custom prices |
StochasticExp(H, L, C, StochLen, S1, S2, ...) | TA_STOCH(0, endIdx, H, L, C, StochLen, S1, TA_MAType_EMA, S2, TA_MAType_EMA, &outBeg, &outNB, outSlowK, outSlowD) | Use TA_MAType_EMA for exponential smoothing |
ADXR(Length) | TA_ADXR(0, endIdx, inHigh, inLow, inClose, length, &outBeg, &outNB, outAdxr) | Direct equivalent; ADX Rating |
ADXCustom(H, L, C, Length) | TA_ADX(0, endIdx, H, L, C, length, &outBeg, &outNB, outAdx) | Custom price arrays |
DMI(Length) | TA_ADX(0, endIdx, inHigh, inLow, inClose, length, &outBeg, &outNB, outAdx) | Wrapper; same as ADX |
DMIPlusCustom(H, L, C, Length) | TA_PLUS_DI(0, endIdx, H, L, C, length, &outBeg, &outNB, outPlusDI) | +DI with custom prices |
DMIMinusCustom(H, L, C, Length) | TA_MINUS_DI(0, endIdx, H, L, C, length, &outBeg, &outNB, outMinusDI) | −DI with custom prices |
ParabolicCustom(AfStep, AfLimit) | TA_SAR(0, endIdx, inHigh, inLow, AfStep, AfLimit, &outBeg, &outNB, outSar) | Same as Parabolic with explicit limit |
TRIX(Close, Length) | TA_TRIX(0, endIdx, inClose, length, &outBeg, &outNB, outTrix) | Direct equivalent |
MassIndex(SmoothLen, SumLen) | Manual: TA_EMA of range, ratio, then sum | No single TA-Lib function; chain EMA calls |
EaseOfMovement | Manual: distance moved / box ratio | No TA-Lib function; compute from OHLCV |
SwingIndex | Manual: Wilder swing index formula | No TA-Lib function; ~20 lines of C++ |
AccumSwingIndex | Manual: running sum of SwingIndex | No TA-Lib function |
Detrend(Close, Length) | Manual: close minus offset SMA | No direct TA-Lib; use TA_SMA with offset |
PercentChange(Close, Length) | TA_ROC(0, endIdx, inClose, length, &outBeg, &outNB, outRoc) | Same as Rate of Change |
UlcerIndex(Close, Length) | Manual: RMS of drawdown percentage | No TA-Lib function; track max, compute pct drawdown |
ParabolicSAR(AfStep, AfLimit, ...) | TA_SAR(0, endIdx, inHigh, inLow, AfStep, AfLimit, &outBeg, &outNB, outSar) + manual position tracking | Track sign changes in SAR vs price for position/transition |
LinearReg(Close, Length, TgtBar, ...) | TA_LINEARREG(...) + TA_LINEARREG_SLOPE(...) + TA_LINEARREG_ANGLE(...) + TA_LINEARREG_INTERCEPT(...) | Combine four TA-Lib calls for multi-output |
TrueRangeCustom(H, L, C) | TA_TRANGE(0, endIdx, H, L, C, &outBeg, &outNB, outTRange) | Custom price arrays |
VolatilityStdDev(NumDays) | Manual: compute log returns, then TA_STDDEV × sqrt(252) | Annualized historical volatility |
StandardDevAnnual(Close, Length, DataType) | TA_STDDEV(0, endIdx, inClose, length, 1.0, &outBeg, &outNB, outStdDev) then multiply by sqrt(252) | Annualize the output |
HighestFC(Close, Length) | TA_MAX(0, endIdx, inClose, length, &outBeg, &outNB, outMax) | Same as Highest |
LowestFC(Close, Length) | TA_MIN(0, endIdx, inClose, length, &outBeg, &outNB, outMin) | Same as Lowest |
PivotHighVS(Inst, Price, LStr, RStr, Len) | Manual: scan for bar higher than L bars left and R bars right | Asymmetric pivot detection |
PivotLowVS(Inst, Price, LStr, RStr, Len) | Manual: scan for bar lower than L bars left and R bars right | Same for lows |
PivotHighVSBar(Inst, Price, LStr, RStr, Len) | Manual: return offset of detected pivot high | bars-ago index |
PivotLowVSBar(Inst, Price, LStr, RStr, Len) | Manual: return offset of detected pivot low | bars-ago index |
Divergence(P1, P2, Str, Len, HiLo) | Manual: compare pivot highs/lows of two series | No TA-Lib function |
TimeSeriesForecast(Close, Length) | TA_TSF(0, endIdx, inClose, length, &outBeg, &outNB, outTsf) | Direct equivalent |
SummationFC(Close, Length) | TA_SUM(0, endIdx, inClose, length, &outBeg, &outNB, outSum) | Same as Summation |
OpenD(N) | Manual: aggregate intraday bars into daily struct DailyBar, index daily_bars[size-1-N].open | Requires daily bar aggregation |
HighD(N) | Manual: daily_bars[size-1-N].high | Daily high from aggregation |
LowD(N) | Manual: daily_bars[size-1-N].low | Daily low |
CloseD(N) | Manual: daily_bars[size-1-N].close | Daily close |
OpenW(N) | Manual: aggregate to weekly bars | Same pattern |
HighW(N) | Manual: weekly high | Weekly aggregation |
LowW(N) | Manual: weekly low | Weekly aggregation |
CloseW(N) | Manual: weekly close | Weekly aggregation |
OpenM(N) | Manual: aggregate to monthly bars | Monthly aggregation |
HighM(N) | Manual: monthly high | Monthly aggregation |
LowM(N) | Manual: monthly low | Monthly aggregation |
CloseM(N) | Manual: monthly close | Monthly aggregation |
OpenY(N) | Manual: aggregate to yearly bars | Yearly aggregation |
HighY(N) | Manual: yearly high | Yearly aggregation |
LowY(N) | Manual: yearly low | Yearly aggregation |
CloseY(N) | Manual: yearly close | Yearly aggregation |
LRO(Cond, Length, N) | Manual: scan from length bars ago forward, find Nth true | Least Recent Occurrence |
SummationIf(Cond, Price, Length) | Manual: for (int i = 0; i < length; i++) if (cond[end-i]) sum += price[end-i]; | Conditional rolling sum |
IFFString(Cond, TrueStr, FalseStr) | cond ? trueStr : falseStr | C++ ternary with std::string |
OBV | TA_OBV(0, endIdx, inClose, inVolume, &outBeg, &outNB, outObv) | Direct equivalent |
VolumeROC(Length) | TA_ROC(0, endIdx, inVolume, length, &outBeg, &outNB, outRoc) | Apply ROC to volume array |
VolumeOsc(ShortLen, LongLen) | Manual: TA_SMA(volume, ShortLen) - TA_SMA(volume, LongLen) | Difference of two volume SMAs |
PriceVolTrend | Manual: cumulative (close[i] - close[i-1]) / close[i-1] * volume[i] | Running sum |
LWAccDis | Manual: cumulative (close - open) / (high - low) * volume | Larry Williams A/D |
Fisher(Price) | Manual: normalize, then 0.5 * log((1 + norm) / (1 - norm)) | Fisher transformation |
FisherINV(Price) | Manual: (exp(2 * price) - 1) / (exp(2 * price) + 1) | Inverse Fisher |
C_Doji(Pct) | TA_CDLDOJI(0, endIdx, inOpen, inHigh, inLow, inClose, &outBeg, &outNB, outInt) | TA-Lib CDL* functions return ±100 |
C_Hammer_HangingMan(Len, Factor, ...) | TA_CDLHAMMER(...) / TA_CDLHANGINGMAN(...) | Separate TA-Lib functions for each pattern |
C_BullEng_BearEng(Len, ...) | TA_CDLENGULFING(0, endIdx, inOpen, inHigh, inLow, inClose, &outBeg, &outNB, outInt) | +100=bullish, −100=bearish |
C_BullHar_BearHar(Len, ...) | TA_CDLHARAMI(0, endIdx, inOpen, inHigh, inLow, inClose, &outBeg, &outNB, outInt) | +100=bullish, −100=bearish |
C_MornDoji_EveDoji(Len, Pct, ...) | TA_CDLMORNINGDOJISTAR(...) / TA_CDLEVENINGDOJISTAR(...) | Separate functions |
C_MornStar_EveStar(Len, ...) | TA_CDLMORNINGSTAR(...) / TA_CDLEVENINGSTAR(...) | Separate functions |
C_PierceLine_DkCloud(Len, ...) | TA_CDLPIERCING(...) / TA_CDLDARKCLOUDCOVER(...) | Separate functions |
C_ShootingStar(Len, Factor) | TA_CDLSHOOTINGSTAR(0, endIdx, inOpen, inHigh, inLow, inClose, &outBeg, &outNB, outInt) | Direct equivalent |
C_3WhSolds_3BlkCrows(Len, Factor, ...) | TA_CDL3WHITESOLDIERS(...) / TA_CDL3BLACKCROWS(...) | Separate functions |
| Statistical extended | ||
AvgDeviation(Close, N) | Manual: mean absolute deviation | No TA-Lib equivalent |
Variance(Close, N) | TA_VAR(0, endIdx, inClose, N, 1.0, &outBeg, &outNB, outVar) | Population variance; the 1.0 is the required double optInNbDev argument |
Kurtosis(Close, N) | Manual: 4th moment calculation | No TA-Lib equivalent |
Skew(Close, N) | Manual: 3rd moment calculation | No TA-Lib equivalent |
PercentRank(ValToRank, Price, N) | Manual: count values ≤ target / N | No TA-Lib equivalent |
Covariance(P1, P2, N) | Manual: sum((P1-mean1)*(P2-mean2)) / N | No TA-Lib equivalent |
Quartile(Close, N, Q) | Manual: sort window, pick percentile | No TA-Lib equivalent |
TrimMean(Close, N, Pct) | Manual: sort, trim edges, average | No TA-Lib equivalent |
Mode(Close, N, Type) | Manual: frequency count over window | No TA-Lib equivalent |
HarmonicMean(Close, N) | Manual: N / sum(1/x) | No TA-Lib equivalent |
| Moving averages extended | ||
SmoothedAverage(Close, N) | Manual: Wilder smoothing prev*(N-1)/N + val/N | Same formula as TA-Lib's internal Wilder smoothing |
| Miscellaneous | ||
BarAnnualization | Manual: compute from bar frequency | Bars-per-year factor |
LastBarOnChart | bar_index == data_size - 1 | True on last bar |
| Custom functions | ||
StochRSI(Close, N, M) | TA_STOCHRSI(0, sz-1, close, N, M, 1, TA_MAType_SMA, ...) | Stochastic RSI |
supertrend(N, Mult) | Manual: ATR bands + direction flip logic | No TA-Lib equivalent |
NVI(Start) | Manual: accumulate on volume-down bars | No TA-Lib equivalent |
PVI(Start) | Manual: accumulate on volume-up bars | No TA-Lib equivalent |
Coppo(N1, N2, N3) | Manual: TA_WMA of two TA_ROC results | Coppock Curve |
LWTI(Close, P, N) | Manual: (sma(diff, N) / sma(range, N)) * 50 + 50 | No TA-Lib equivalent |
TVI(Close, Vol, Tick) | Manual: cumulative directional volume | No TA-Lib equivalent |
SharpeRatio(Period, Rate, Calc, Cap) | Manual: (avg_return - rf) / std_return | Portfolio Sharpe |
WRSI(N, Close) | TA_RSI(0, sz-1, close, N, ...) | TA-Lib RSI uses Wilder smoothing |
NewMA(Close, N) | Manual: Heikin-Ashi + triple EMA hybrid | No TA-Lib equivalent |
Table 4 — Strategy Orders
| PowerLanguage | C++ | Notes |
|---|---|---|
Buy("label") next bar market | orders.push_back({"label", Side::Long, OrderType::Market, 0.0, 1.0}) | Push to the orders vector |
SellShort("label") next bar market | Order{"label", Side::Short, OrderType::Market, 0.0, 1.0} | Side::Short for short entry |
Sell("label") next bar market | Close long: set position_ = 0 or push a close-position variant | WARNING: PL Sell exits a long — do NOT map to Side::Short |
BuyToCover("label") next bar market | Close short: set position_ = 0 | PL BuyToCover exits an existing short |
Buy("label") next bar at price limit | Order{"label", Side::Long, OrderType::Limit, price, 1.0} | Limit order |
Buy("label") next bar at price stop | Order{"label", Side::Long, OrderType::Stop, price, 1.0} | Stop order |
SetStopLoss(dollars) | stop_price = entry_price_ - dollars / (qty * point_value) | PL takes a dollar amount; C++ must convert to price level |
SetProfitTarget(dollars) | target_price = entry_price_ + dollars / (qty * point_value) | Same dollar-to-price conversion |
Table 5 — Plotting
| PowerLanguage | C++ | Notes |
|---|---|---|
Plot1(value, "label") | std::cout << label << ": " << value << "\n" or log to file | No chart in C++; output to stdout, file, or results container |
SetPlotColor(1, Red) | N/A | No visual output; skip or store as metadata |
SetPlotWidth(1, 2) | N/A | Skip |
NoPlot(1) | skip output for that bar | Conditional: don't write to results |
Table 6 — Control Flow
| PowerLanguage | C++ | Notes |
|---|---|---|
If cond Then Begin ... End; | if (cond) { ... } | C++ requires parentheses around condition |
If cond Then ... Else ... | if (cond) { ... } else { ... } | Direct mapping |
For idx = 1 to n Begin ... End; | for (int i = 1; i <= n; ++i) { ... } | PL For is inclusive; use <= in C++. PL side uses idx because i is reserved (alias for OpenInterest) |
While cond Begin ... End; | while (cond) { ... } | Direct mapping |
Switch (expr) Begin Case 1: ... End; | switch (expr) { case 1: ... break; default: break; } | C++ switch requires break; to prevent fallthrough; PL empty case body is a compile error — use Value1 = Value1; as no-op |
Once Begin ... End; | if (first_bar_) { ... first_bar_ = false; } | Use a bool member; PL Once runs on first bar only |
Table 7 — Other Built-ins and Features
| PowerLanguage | C++ | Notes |
|---|---|---|
MarketPosition | position_ (int: −1/0/+1) | Track manually as member; update on fills |
EntryPrice | entry_price_ (double) | Track manually |
CurrentContracts | current_contracts_ (double) | Track manually |
BarsSinceEntry | bars_since_entry_ (int) | Increment each bar while in position; reset on entry |
Print("text") | std::cout << "text" << std::endl | Use iostream or fprintf |
#BeginCmtry ... #EndCmtry | No equivalent | Skip or log |
Alert("msg") | std::cerr << "ALERT: msg" << std::endl or callback | Design depends on framework |
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 (size_t i = 0; i + 1 < bars.size(); ++i) {
orders.clear();
std::vector<Bar> history(bars.begin(), bars.begin() + i + 1);
strategy.on_bar(history, orders); // orders queued on bar i...
const Bar& next = bars[i + 1];
for (const Order& o : orders) { // ...filled on bar i+1
if (o.type == OrderType::Market)
fill(o, next.open); // next bar's OPEN
else if (o.type == OrderType::Stop && next.high >= o.price) // buy stop
fill(o, std::max(o.price, next.open)); // intrabar at the stop price
// sell stop: next.low <= o.price; limit orders mirror with reversed comparisons
}
}
EMA seeding/warmup gotcha: PL XAverage seeds recursively from the first bar's price; TA-Lib TA_EMA seeds with the SMA of the first Length values — early values differ. Other targets differ again (pandas-ta SMA seed by default, ta-rs first-value seed, Pine na during warmup). Discard roughly the first 3×Length bars before comparing backtests.
-
Selldoes not mean "go short". In PowerLanguage,Sellexits an existing long position. The C++ equivalent is closing the position (settingposition_to 0). Do not create a new short entry as a translation ofSell. -
PL is implicitly bar-driven; C++ requires an explicit loop. PowerLanguage executes the entire script top-to-bottom on each bar close automatically. In C++, you must write the
for (size_t i = 0; i < bars.size(); ++i)loop and callon_bar()on each iteration. -
Bar lookback causes undefined behavior without bounds checking. PL uses MaxBarsBack to prevent execution on bars with insufficient history — the script simply does not run on those early bars. C++
bars[bars.size() - 6]is undefined behavior whenbars.size() < 6— likely a crash, but possibly silent corruption. Always checkbars.size() > nbefore lookback access, and skip the bar or use a default value when history is insufficient. -
TA-Lib uses batch computation, not streaming. TA-Lib functions like
TA_SMAprocess an entire array at once and returnoutBegIdx(first valid output index) andoutNBElement(count of valid outputs). Calling TA-Lib on every bar with a growing history slice recomputes the entire series each time. For performance, consider TA-Lib RT's streaming API or caching previous results. -
TA-Lib
outBegIdxoffset is the most common TA-Lib mistake. The output array is NOT aligned with the input array. The first valid output is atoutput[0], which corresponds toinput[outBegIdx]. The current bar's value isoutput[outNBElement - 1]. Ignoring this offset and readingoutput[inputIndex]produces incorrect indicator values. -
Crossover/crossunder has no TA-Lib built-in. PL
Crosses Over/Crosses Underare language keywords. TA-Lib has no crossover function. Implement as:bool crossed_over = prev_a <= prev_b && a > b;. Store previous-bar values as member variables. -
Dollar amounts versus price levels for stops.
SetStopLossandSetProfitTargetin PL accept a dollar amount. C++ must convert to price levels explicitly:stop_price = entry_price_ - stop_dollars / (qty * point_value). -
Position state is not tracked automatically. PL provides
MarketPosition,EntryPrice,CurrentContracts, andBarsSinceEntryas built-in variables updated by the engine. In C++, you must maintain these as class members and update them manually on order fills. -
C++ has no
Value1..Value99orCondition1..Condition99. Replace with named, typed class members using descriptive identifiers. -
TA-Lib output arrays must be pre-allocated. All
TA_*functions write into caller-allocated arrays. Size them at least as large as the input array. Failing to allocate enough space causes a buffer overflow. Usestd::vectorinstead of raw arrays to avoid this class of bugs. -
Multi-data series requires explicit design. PL
Close of Data2accesses a second feed bound to the chart. In C++, passconst std::vector<Bar>& bars2as a second parameter. There is no implicit secondary-feed mechanism. -
No Portfolio Money Management (PMM) equivalent. PL's PMM allows a single script to govern position sizing across multiple instruments. C++ has no built-in portfolio abstraction. You must implement cross-instrument logic as a separate portfolio manager that calls individual strategy instances.
Part 3: Conversion Checklists
PL → C++ Pre-Conversion
- List every
Inputs:declaration; note name, type, default — each becomes a constructor parameter and private member - List every
Variables:declaration; note initial values — each becomes a private member initialized in-class or in the constructor - Identify all
Value1..Value99andCondition1..Condition99usages; plan meaningful replacement names - Identify all indicator calls; confirm each has a TA-Lib equivalent (check
ta_func.hfor the function name) - Locate all
Data2/Data3references; decide on multi-feed architecture - Locate all
SetStopLoss,SetProfitTarget, and dollar-based parameters; note point value for conversion - Check for
IntraBarPersistvariables; decide whether tick-level behavior matters - Identify all
.elffunction calls; plan conversion to C++ free functions or member functions
PL → C++ Post-Conversion
- Every
Sellmaps to closing the long, not entering short — audit all order keywords - Every
BuyToCovermaps to closing the short, not entering long - All lookback accesses (
Close[N]) guarded withbars.size() > n - All TA-Lib output arrays properly pre-allocated (use
std::vector, not raw arrays) - All TA-Lib results read from
output[outNBElement - 1], notoutput[inputIndex]—outBegIdxoffset accounted for -
Crosses Over/Crosses Underimplemented as two-bar comparisons -
MarketPosition,EntryPrice,CurrentContracts,BarsSinceEntrytracked as manually updated members - Dollar-based stop/target amounts converted to price levels
-
Value1..Value99andCondition1..Condition99renamed to descriptive typed members - Code compiles with
g++ -std=c++17 -Wall -Wextra -Werrorwith zero errors and zero warnings - Strategy back-tested on the same instrument and date range as PL original; trade count and net P&L in the same order of magnitude
C++ → PL Pre-Conversion
- List all class members and categorize: inputs (const/set-once) vs variables (mutable) vs indicator state
- Identify all TA-Lib function calls; map each to the PL built-in (TA_SMA → Average, TA_EMA → XAverage, TA_RSI → RSI, etc.)
- Check for
switchstatements with fallthrough — PLSwitchdoes not fallthrough - Identify any C++ libraries beyond TA-Lib; plan PL equivalents or inline reimplementation
- Check for explicit position-tracking members; confirm they can be replaced by PL's built-in
MarketPosition,EntryPrice, etc.
C++ → PL Post-Conversion
- Every short entry maps to
SellShort, notSell - Every long close maps to
Sell, notSellShort - All
bars[bars.size() - 1 - n].closelookbacks converted toClose[n] - All manual crossover comparisons converted to PL
Crosses Over/Crosses Underkeywords - All class members categorized: const inputs →
Inputs:, mutable state →Variables: - All TA-Lib batch calls replaced with PL function calls (e.g.,
TA_SMA(...)→Average(Close, Length)) - TA-Lib
outBegIdx/outNBElementindex arithmetic eliminated — PL handles lookback alignment internally - Price-level stops/targets converted to dollar amounts for
SetStopLoss/SetProfitTarget - Script compiled in MultiCharts PowerEditor with zero errors before testing