As a developer who frequently works with trading algorithms and indicators, I recently undertook the task of converting the SSL Hybrid indicator from PineScript to AmiBroker AFL code. This indicator, popular among traders for its versatility and effectiveness, posed a significant challenge due to the differences in scripting languages and the complexity of the functions involved.
To assist in this conversion, I decided to utilize OpenAI’s new reasoning model, o1-preview, to see how it could facilitate the process. Having previously used the GPT-4 model, I was curious to compare the two in terms of accuracy, efficiency, and the ability to handle complex coding tasks.

The Challenge of Code Conversion
Converting code between programming languages is rarely a straightforward task, especially when dealing with complex trading indicators like SSL Hybrid. Each language has its own syntax, functions, and limitations. PineScript and Amibroker AFL, while both designed for financial analysis, have different capabilities and conventions.
One of the primary challenges I faced was handling self-referencing variables. In PineScript, recursive variables are commonly used, but Amibroker AFL doesn’t support self-referencing variables in the same way. This meant that certain constructs had to be reimagined using loops or alternative logic.
Another hurdle was dealing with functions that exist in PineScript but not in Amibroker AFL. For example, functions like strsplit
, TrueRange()
, or certain moving average functions needed to be either redefined or replaced with equivalent AFL code.
SSL Hybird – Amibroker AFL Code
// Title: SSL Hybrid
// Converted from PineScript to AmiBroker AFL
// Prompt by Rajandran R (Creator - OpenAlgo / Founder - Marketcalls)
// Coding done by ChatGPT o1-preview model
// Date - 13th Sep 2024
_SECTION_BEGIN("SSL Hybird Indicator");
// Inputs
show_Baseline = ParamToggle("Show Baseline", "No|Yes", 1);
show_SSL1 = ParamToggle("Show SSL1", "No|Yes", 0);
show_atr = ParamToggle("Show ATR bands", "No|Yes", 1);
// ATR Parameters
atrlen = Param("ATR Period", 14, 1, 100, 1);
mult = Param("ATR Multi", 1, 0.1, 10, 0.1);
// ATR Smoothing
smoothing_options = "RMA|SMA|EMA|WMA";
smoothing_default = 2; // 0-based index: 0=RMA, 1=SMA, 2=EMA, 3=WMA (Default to EMA)
smoothing = ParamList("ATR Smoothing", smoothing_options, smoothing_default);
// Moving Average Types
maType_options = "SMA|EMA|DEMA|TEMA|LSMA|WMA|MF|VAMA|TMA|HMA|JMA|Kijun v2|EDSMA|McGinley";
maType_default = 9; // 0-based index: HMA is the 10th option (Default)
maType = ParamList("SSL1 / Baseline Type", maType_options, maType_default);
len = Param("SSL1 / Baseline Length", 60, 1, 200, 1);
SSL2Type_options = "SMA|EMA|DEMA|TEMA|WMA|MF|VAMA|TMA|HMA|JMA|McGinley";
SSL2Type_default = 8; // HMA is the 9th option (0-based index)
SSL2Type = ParamList("SSL2 / Continuation Type", SSL2Type_options, SSL2Type_default);
len2 = Param("SSL 2 Length", 5, 1, 100, 1);
SSL3Type_options = "DEMA|TEMA|LSMA|VAMA|TMA|HMA|JMA|Kijun v2|McGinley|MF";
SSL3Type_default = 5; // HMA is the 6th option (0-based index)
SSL3Type = ParamList("EXIT Type", SSL3Type_options, SSL3Type_default);
len3 = Param("EXIT Length", 15, 1, 100, 1);
src = ParamField("Source", 3); // Default to Close
// Additional Parameters
kidiv = Param("Kijun MOD Divider", 1, 1, 4, 1);
jurik_phase = Param("Jurik Phase", 3, -100, 100, 1);
jurik_power = Param("Jurik Power", 1, 1, 10, 1);
volatility_lookback = Param("Volatility Lookback Length", 10, 1, 100, 1);
beta = Param("Modular Filter Beta", 0.8, 0, 1, 0.1);
feedback = ParamToggle("Modular Filter Feedback", "No|Yes", 0);
z = Param("Modular Filter Feedback Weighting", 0.5, 0, 1, 0.1);
ssfLength = Param("EDSMA - Super Smoother Filter Length", 20, 1, 100, 1);
ssfPoles_options = "2|3";
ssfPoles_default = 0; // 0-based index: Default is "2"
ssfPoles = ParamList("EDSMA - Super Smoother Filter Poles", ssfPoles_options, ssfPoles_default);
// Precompute EMA value for McGinley function
EMA_value = EMA(src, len);
// McGinley Dynamic Average Function
function McGinley(src, len)
{
mg = Null;
mg[0] = EMA_value[0];
for (i = 1; i < BarCount; i++)
{
if (mg[i - 1] != 0)
mg[i] = mg[i - 1] + (src[i] - mg[i - 1]) / (len * (src[i] / mg[i - 1]) ^ 4);
else
mg[i] = mg[i - 1];
}
return mg;
}
// Kijun v2 Function
function Kijun(len, kidiv)
{
kijun = (HHV(High, len) + LLV(Low, len)) / 2;
len_div = len / kidiv;
conversionLine = (HHV(High, len_div) + LLV(Low, len_div)) / 2;
delta = (kijun + conversionLine) / 2;
return delta;
}
// VAMA - Volatility Adjusted MA Function
function VAMA(src, len, volatility_lookback)
{
mid = EMA(src, len);
dev = src - mid;
vol_up = HHV(dev, volatility_lookback);
vol_down = LLV(dev, volatility_lookback);
result = mid + (vol_up + vol_down) / 2;
return result;
}
// Function for ATR Smoothing
if (smoothing == "RMA")
atr_slen = Wilders(ATR(1), atrlen);
else if (smoothing == "SMA")
atr_slen = MA(ATR(1), atrlen);
else if (smoothing == "EMA")
atr_slen = EMA(ATR(1), atrlen);
else if (smoothing == "WMA")
atr_slen = WMA(ATR(1), atrlen);
else
atr_slen = EMA(ATR(1), atrlen); // Default to EMA if not specified
// ATR Up/Low Bands
upper_band = Close + mult * atr_slen;
lower_band = Close - mult * atr_slen;
// Function for various Moving Averages
function CustomMA(type, src, len)
{
result = Null;
if (type == "SMA")
result = MA(src, len);
else if (type == "EMA")
result = EMA(src, len);
else if (type == "DEMA")
result = DEMA(src, len);
else if (type == "TEMA")
result = TEMA(src, len);
else if (type == "LSMA")
result = LinearReg(src, len);
else if (type == "WMA")
result = WMA(src, len);
else if (type == "TMA")
{
len1 = ceil(len / 2);
len2 = floor(len / 2) + 1;
result = MA(MA(src, len1), len2);
}
else if (type == "HMA")
{
len2 = int(len / 2);
hmalen = Round(Sqrt(len));
result = WMA(2 * WMA(src, len2) - WMA(src, len), hmalen);
}
else if (type == "McGinley")
result = McGinley(src, len);
else if (type == "Kijun v2")
result = Kijun(len, kidiv);
else if (type == "VAMA")
result = VAMA(src, len, volatility_lookback);
else
result = Null;
return result;
}
// Compute Moving Averages
emaHigh = CustomMA(maType, High, len);
emaLow = CustomMA(maType, Low, len);
// Initialize Hlv
Hlv = Null;
Hlv[0] = IIf(Close[0] > emaHigh[0], 1, IIf(Close[0] < emaLow[0], -1, 0));
// Compute Hlv
for (i = 1; i < BarCount; i++)
{
if (Close[i] > emaHigh[i])
Hlv[i] = 1;
else if (Close[i] < emaLow[i])
Hlv[i] = -1;
else
Hlv[i] = Hlv[i - 1];
}
// Compute sslDown
sslDown = IIf(Hlv < 0, emaHigh, emaLow);
// Similarly compute Hlv2 and sslDown2
maHigh = CustomMA(SSL2Type, High, len2);
maLow = CustomMA(SSL2Type, Low, len2);
Hlv2 = Null;
Hlv2[0] = IIf(Close[0] > maHigh[0], 1, IIf(Close[0] < maLow[0], -1, 0));
for (i = 1; i < BarCount; i++)
{
if (Close[i] > maHigh[i])
Hlv2[i] = 1;
else if (Close[i] < maLow[i])
Hlv2[i] = -1;
else
Hlv2[i] = Hlv2[i - 1];
}
sslDown2 = IIf(Hlv2 < 0, maHigh, maLow);
// Compute Hlv3 and sslExit
ExitHigh = CustomMA(SSL3Type, High, len3);
ExitLow = CustomMA(SSL3Type, Low, len3);
Hlv3 = Null;
Hlv3[0] = IIf(Close[0] > ExitHigh[0], 1, IIf(Close[0] < ExitLow[0], -1, 0));
for (i = 1; i < BarCount; i++)
{
if (Close[i] > ExitHigh[i])
Hlv3[i] = 1;
else if (Close[i] < ExitLow[i])
Hlv3[i] = -1;
else
Hlv3[i] = Hlv3[i - 1];
}
sslExit = IIf(Hlv3 < 0, ExitHigh, ExitLow);
// Compute base_cross_Long and base_cross_Short
base_cross_Long = Cross(Close, sslExit);
base_cross_Short = Cross(sslExit, Close);
codiff = IIf(base_cross_Long, 1, IIf(base_cross_Short, -1, Null));
// Keltner Baseline Channel
useTrueRange = True;
multy = Param("Base Channel Multiplier", 0.2, 0, 10, 0.05);
Keltma = CustomMA(maType, src, len);
range = IIf(useTrueRange, ATR(1), High - Low);
rangema = EMA(range, len);
upperk = Keltma + rangema * multy;
lowerk = Keltma - rangema * multy;
// Baseline Violation Candle
difference = abs(Close - Open);
atr_violation = difference > atr_slen;
InRange = (upper_band > Keltma) AND (lower_band < Keltma);
candlesize_violation = atr_violation AND InRange;
// Plot shapes
PlotShapes(IIf(candlesize_violation, shapeCircle, shapeNone), colorWhite, 0, High, Offset=10);
// Colors for bar coloring
color_up = ColorRGB(0,195,255); // #00c3ff
color_down = ColorRGB(255,0,98); // #ff0062
color_neutral = colorGrey50; // Use a grey color
// Colors
show_color_bar = ParamToggle("Color Bars", "No|Yes", 1);
color_bar = IIf(Close > upperk, color_up, IIf(Close < lowerk, color_down, color_neutral));
color_ssl1 = IIf(Close > sslDown, color_up, IIf(Close < sslDown, color_down, Null));
// Set bar colors
if (show_color_bar)
{
SetBarFillColor(color_bar);
}
// Plots
if (show_Baseline)
{
Plot(Keltma, "MA Baseline", colorBlue, styleThick);
Plot(upperk, "Baseline Upper Channel", colorLightBlue);
Plot(lowerk, "Baseline Lower Channel", colorLightBlue);
}
if (show_SSL1)
{
Plot(sslDown, "SSL1", color=color_ssl1, styleThick);
}
if (show_atr)
{
Plot(upper_band, "+ATR", colorGrey50);
Plot(lower_band, "-ATR", colorGrey50);
}
// SSL2 Continuation from ATR
atr_crit = Param("Continuation ATR Criteria", 0.9, 0, 10, 0.1);
upper_half = Close + atr_slen * atr_crit;
lower_half = Close - atr_slen * atr_crit;
buy_inatr = lower_half < sslDown2;
sell_inatr = upper_half > sslDown2;
sell_cont = (Close < Keltma) AND (Close < sslDown2);
buy_cont = (Close > Keltma) AND (Close > sslDown2);
sell_atr = sell_inatr AND sell_cont;
buy_atr = buy_inatr AND buy_cont;
atr_fill = IIf(buy_atr, colorGreen, IIf(sell_atr, ColorRGB(157,0,255), colorWhite));
Plot(sslDown2, "SSL2", color=atr_fill, style=styleDots);
// Define Buy and Short signals for plotting arrows
Buy = codiff == 1;
Short = codiff == -1;
/* Plot Buy and Sell Signal Arrows */
PlotShapes(IIf(Buy, shapeSquare, shapeNone),colorGreen, 0, L, Offset=-40);
PlotShapes(IIf(Buy, shapeSquare, shapeNone),colorLime, 0,L, Offset=-50);
PlotShapes(IIf(Buy, shapeUpArrow, shapeNone),colorWhite, 0,L, Offset=-45);
PlotShapes(IIf(Short, shapeSquare, shapeNone),colorRed, 0, H, Offset=40);
PlotShapes(IIf(Short, shapeSquare, shapeNone),colorOrange, 0,H, Offset=50);
PlotShapes(IIf(Short, shapeDownArrow, shapeNone),colorWhite, 0,H, Offset=-45);
// Note: Complex functions like JMA, MF, and EDSMA are not implemented here and require custom coding or external libraries.
_SECTION_END();
// Price Plotting
_SECTION_BEGIN("Price");
SetChartOptions(0, chartShowArrows | chartShowDates);
_N(Title = StrFormat("{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}}", O, H, L, C, SelectedValue(ROC(C, 1))));
Plot(C, "Close", color_bar, styleBar,styleThick,width=3);
_SECTION_END();
Hallucinations and Challenges
One of the primary issues I encountered during the conversion process was the model’s tendency to hallucinate—that is, to generate code or functions that don’t exist or are not applicable in the context of AmiBroker AFL. For instance:
- Non-existent Functions: The model sometimes referenced functions like
StrSplit
,TrueRange()
, orPow()
, which are not part of the AmiBroker AFL language. This required careful cross-referencing with the AFL documentation to ensure that all functions used were valid. - Incorrect Syntax: There were instances where the model used incorrect syntax, such as attempting to use switch statements with string expressions, which is not supported in AFL. Adjustments had to be made to replace these with appropriate
if...else
statements. - Self-Referencing Variables: AFL does not support self-referencing variables in the same way PineScript does. The model initially didn’t account for this, and I had to manually implement loops to handle variables like
Hlv
,Hlv2
, andHlv3
. - Parameter Handling: The model sometimes mishandled parameter lists, especially with default values in
ParamList
. It required setting the default value as an integer index corresponding to the options provided.
Despite these challenges, the model was able to provide a substantial starting point for the conversion. The key was to remain vigilant and validate each part of the code against the AFL documentation.
Improvements Over GPT-4
Compared to the GPT-4 model, o1-preview showed noticeable improvements in several areas:
- Enhanced Reasoning Ability: The o1-preview model demonstrated a better grasp of complex logical structures, making it more adept at handling the intricate parts of the code conversion.
- Adaptability: It was more flexible in adapting to corrections. When pointed out, it adjusted the code more accurately to reflect the requirements of AFL.
- Detail Orientation: The model provided more detailed explanations of the code, which helped in understanding its reasoning and in identifying where adjustments were necessary.
However, while o1-preview showed these improvements, it wasn’t without its shortcomings. The hallucinations, although fewer, still posed a significant hurdle, emphasizing the need for human oversight.
The Thinking Process
The o1-preview model approaches code conversion by attempting to map functions and constructs from one language to another based on context and known equivalents. It leverages its training data to find the best matches for functions and syntax.
For example, when converting moving average functions, it tries to find the AFL equivalent of PineScript’s functions, adjusting parameters as needed. In cases where a direct equivalent doesn’t exist, the model attempts to recreate the functionality using available AFL constructs.
The model also tries to explain its reasoning by commenting on the code, which aids in understanding its approach. This is particularly useful when dealing with complex functions that require careful translation.
The Future Ahead
The experience with o1-preview highlights the potential and limitations of AI models in code conversion tasks:
- Potential for Automation: As AI models continue to improve, they could significantly reduce the time required for code conversions, allowing developers to focus on more critical aspects of development.
- Need for Collaboration: AI models are tools that, when used in collaboration with human expertise, can yield excellent results. They provide a foundation that developers can build upon and refine.
- Continuous Improvement: Models like o1-preview will benefit from ongoing training and feedback. By exposing them to more code and diverse scenarios, their ability to accurately perform such tasks will increase.
- Handling Complexity: Future models may overcome current limitations, such as hallucinations and syntax errors, by incorporating more robust validation mechanisms and better understanding of programming languages.
Conclusion
Converting the SSL Hybrid indicator from PineScript to AmiBroker AFL using OpenAI’s o1-preview model was an enlightening experience. While the model showcased improvements over previous versions, it also underscored the importance of human involvement in the process.
The journey highlighted the exciting possibilities that AI brings to software development, especially in automating tedious tasks. With further advancements, models like o1-preview could become indispensable tools for developers, streamlining workflows and fostering innovation.
Disclaimer: This blog post reflects my personal experience and observations while using OpenAI’s o1-preview model for code conversion tasks.