The Financial Hacker https://financial-hacker.com A new view on algorithmic trading Mon, 16 Mar 2026 10:12:24 +0000 en-US hourly 1 https://financial-hacker.com/wp-content/uploads/2017/07/cropped-mask-32x32.jpg The Financial Hacker https://financial-hacker.com 32 32 The One Euro Filter https://financial-hacker.com/the-one-euro-filter/ https://financial-hacker.com/the-one-euro-filter/#comments Mon, 16 Mar 2026 10:07:33 +0000 https://financial-hacker.com/?p=4925 Continue reading "The One Euro Filter"]]> Whenever John Ehlers writes about a new indicator, I crack it open and wire it straight into C for the Zorro platform. Or rather, I let ChatGPT do most of the work. The One Euro Filter is a minimalistic, yet surprisingly effective low-latency smoother that reacts instantly to volatility with less lag of the usual adaptive averages. This is achieved by dynamically adapting its time period.

This is the OneEurFilter function in C, converted straight from Ehlers’ EasyLanguage code, the comments left in place:

var OneEurFilter(vars Data, int PeriodMin, var Factor)
{
  vars SmoothedDX = series(Data[0],2), 
    Smoothed = series(Data[0],2);
  var Alpha = 2*PI/(4*PI + 10);
//EMA the Delta Price
  SmoothedDX[0] = Alpha*(Data[0]-Data[1]) + (1.-Alpha)*SmoothedDX[1];
//Adjust cutoff period based on a fraction of the rate of change
  var Cutoff = PeriodMin + Factor*abs(SmoothedDX[0]);
//Compute adaptive alpha
  Alpha = 2*PI/(4*PI + Cutoff);
//Adaptive smoothing
  return Smoothed[0] = Alpha*Data[0] + (1.-Alpha)*Smoothed[1];
}

This is how the OneEurFilter (blue line) looks when applied on a ES chart:

The price curve is well reproduced with almost no lag. Cheap, efficient, low-latency — 1 Euro well spent. The code can be downloaded from the 2026 script repository.

]]>
https://financial-hacker.com/the-one-euro-filter/feed/ 7
Build Better Strategies, Part 6: Evaluation https://financial-hacker.com/build-better-strategies-part-6-evaluation/ https://financial-hacker.com/build-better-strategies-part-6-evaluation/#comments Thu, 05 Feb 2026 16:54:41 +0000 https://financial-hacker.com/?p=4901 Continue reading "Build Better Strategies, Part 6: Evaluation"]]> Developing a successful strategy is a process with many steps, described in the Build Better Strategies article series. At some point you have coded a first, raw version of the strategy. At that stage you’re usually experimenting with different functions for market detection or trade signals. The problem: How can you determine which indicator, filter, or machine learning method  works best with which markets and which time frames? Manually testing all combinations is very time consuming, close to impossible. Here’s a way to run that process automated with a single mouse click.

A robust trading strategy has to meet several criteria:

  • It must exploit a real and significant market inefficiency. Random-walk markets cannot be algo traded.
  • It must work in all market situations. A trend follower must survive a mean reverting regime.
  • It must work under many different optimization settings and parameter ranges.
  • It must be unaffected by random events and price fluctuations.

There are metrics and algorithms to test all this. The robustness under different market situations can be determined through the R2 coefficient or the deviations between the walk forward cycles. The parameter range robustness can be tested with a WFO profile (aka cluster analysis), the price fluctuation robustness with oversampling. A Montecarlo analysis finds out whether the strategy is based on a real market inefficiency.

Some platforms, such as Zorro, have functions for all this. But they require dedicated code in the strategy, often more than for the algorithm itself. In this article I’m going to describe an evaluation framework – a ‘shell’ – that skips the coding part. The evaluation shell is included in the latest Zorro version. It can be simply attached to any strategy script. It makes all strategy variables accessible in a panel and adds stuff that’s common to all strategies – optimization, money management, support for multiple assets and algos, cluster and montecarlo analysis. It evaluates all strategy variants in an automated process and builds the optimal portfolio of combinations from different algorithms, assets, and timeframes. 

The process involves these steps: 

The first step of strategy evaluation is generating sets of parameter settings, named jobs. Any job is a variant of the strategy that you want to test and possibly include in the final portfolio. Parameters can be switches that select between different indicators, or variables (such as timeframes) with optimization ranges. All parameters can be edited in the user interface of the shell, then saved with a mouse click as a job. 

The next step is an automated process that runs through all previously stored jobs, trains and tests any of them with different asset, algo, and time frame combinations, and stores their results in a summary. The summary is a CSV list with the performance metrics of all jobs. It is automatically sorted – the best performing job variants are at the top – and looks like this:

So you can see at a glance which parameter combinations work with which assets and time frames, and which are not worth to examine further. You can repeat this step with different global settings, such as bar period or optimization method, and generate multiple summaries in this way. 

The next step in the process is cluster analysis. Every job in a selected summary is optimized multiple times with different walk-forward settings. The result with any job variant is stored in WFO profiles or heatmaps:

After this process, you likely ended up with a couple survivors in the top of the summary. The surviving jobs have all a positive return, a steady rising equity curve, shallow drawdowns, and robust parameter ranges since they passed the cluster analysis. But any selection process generates selection bias. Your perfect portfolio will likely produce a great backtest, but will it perform equally well in live trading? To find out, you run a Montecarlo analysis, aka ‘Reality Check’.

This is the most important test of all, since it can determine whether your strategy exploits a real market inefficiency. If the Montecarlo analysis fails with the final portfolio, it will likely also fail with any other parameter combination, so you need to run it only close to the end. If your system passes Montecarlo with a p-value below 5%, you can be relatively confident that the system will return good and steady profit in live trading. Otherwise, back to the drawing board.

The use case

For a real life use case,  we generated algorithms for the Z12 system that comes with Zorro. Z12 is a portfolio from several trend and counter trend algorithms that all trade simultaneously. The trading signals are generated with spectral analysis filters. The system trades a subset of Forex pairs and index CFDs on a 4-hour timeframe. The timeframe was choosen for best performance, as were the traded Forex pairs and CFDs. 

We used the evaluation shell to create new algos, not from a selected subset, but from all major Forex pairs and major index CFDs, with 3 different time frames from 60, 120, and 240 minutes. 29 algorithms passed the cluster and montecarlo analysis, of which the least correlated were put into the final portfolio. This is the equity curve of the new Z12 system:

Other performance parameters, such as Profit Factor, Sharpe ratio, Calmar Ratio, and R2 also improved by more than 30%. The annual return almost doubled, compared with the average of the previous years. Nothing in the basic Z12 algorithms has changed. Only new combinations of algos, assets, and timeframes are now traded.

The evaluation shell is included in Zorro version 3.01 or above. Usage and details are described under https://zorro-project.com/manual/en/shell.htm.  Attaching the shell to a strategy is described under https://zorro-project.com/manual/en/shell2.htm.

]]>
https://financial-hacker.com/build-better-strategies-part-6-evaluation/feed/ 2
The Points-and-Line Chart https://financial-hacker.com/the-points-and-line-chart/ https://financial-hacker.com/the-points-and-line-chart/#comments Tue, 14 Oct 2025 13:45:25 +0000 https://financial-hacker.com/?p=4869 Continue reading "The Points-and-Line Chart"]]> Traders like charts with special bars, since they let the price curve appear smoother and more predictable as it really is. Some types of bars, such as Renko bars, even use fake prices for generating curves that appear to move straight upwards or downwards. In the TASC November issue, Mohamed Ashraf and Mohamed Meregy presented the Points and Line Chart that avoids this problem. At least in the standard variant, the prices in the chart are real, and can be used with indicators. In this article I’ll explain the usage of special bars with Zorro, and how ChatGPT can help with generating code.

As far as I know, there is only one trading platform that supports custom bars natively. All other platforms require complicated workarounds. The author’s Amibroker code for the points-and-line chart filled six pages and looked a bit… awful. I did not want to touch it. Fortunately, my new friend Chad offered his help. The prompt:

Dear Chad, please convert the AmiBroker code in this PDF to C for Zorro. Use the bar function for user-defined bars on https://zorro-project.com/manual/en/bar.htm. Generate a DJIA chart with MACD and MACDSignal. For good code I’ll give you $100.

(The $100 reward, in my experience, often improves Chad’s code. In the past, “write good code or I break all your bones” used to work even better, but not anymore since Chad 5.0 came out).

After about 3 minutes, Chad produced a lite-C script. It does still look a bit awful, but far better than the original code. I have cleaned it up at a few places, used different colors for the MACD, and added a ‘g’ to the names of global and static variables since this is my custom. This is the code of the bar function to generate Points-and-Line bars:

//////////////////////////////////////////////////////////
// PointsLine.c — Zorro C port of "Points & Line" chart
// Ashraf & Meregy, TASC Traders' Tips
// Conversion P. Volkova & ChatGPT 5.0
//////////////////////////////////////////////////////////
// Modes
#define SCALE_DEFAULT 0
#define SCALE_ATR 1
#define SCALE_PERCENT 2
#define M_POINTSIZE 0
#define M_HIGHLOW 1
#define M_CLOSE 2

// User parameters
int gReverse = 3; // boxes needed to reverse
int gScale = SCALE_DEFAULT; // 0=Default, 1=ATR(14), 2=Percent
int gMethod = M_CLOSE; // 0=PointSize, 1=HighLow, 2=Close
var gPercent = 1.0; // % for SCALE_PERCENT

// Compute "box size"
var box(var Price)
{
  if(gScale == SCALE_DEFAULT) {
#define RNG(X,Y) if(Price < X) return Y
    RNG(0.25,0.025);
    RNG(0.5,0.05);
    RNG(1,0.1);
    RNG(5,0.25);
    RNG(20,0.5);
    RNG(100,1);
    RNG(200,2);
    RNG(500,5);
    RNG(1000,10);
    RNG(2000,20);
    RNG(5000,50);
    RNG(10000,100);
    RNG(20000,200);
    return 500;
  }
  if(gScale = SCALE_ATR)
    return ATR(14);
  else // SCALE_PERCENT
    return Price*gPercent/100;
}
 
// User-defined bars
function bar(var *Open, var *High, var *Low, var *Close)
{
  var C = Close[0], H = High[0], L = Low[0];
  static int gDir = -1; // initially down
  static var gCF = C, gCR = C, gLF = C, gHR = C;
// box size
  var Box = fix0(box(C));
  var CF = ceil(C/Box)*Box,
  CR = floor(C/Box)*Box,
  LF = ceil(L/Box)*Box,
  HR = floor(H/Box)*Box;
   
  Switch (gMethod)
  {
  case M_POINTSIZE:
    if(CF < gCF && gDir < 0) { // continue down, new box
      gCR = CF - Box; gCF = CF;
      Close[0] = CF; return 1;
    }
    if(gCF + Box*gReverse <= CR && gDir < 0) {
      gCR = CR; gCF = CR + Box;
      Close[0] = CR; gDir = 1; return 1; // swap direction
    }
    if(gCR < CR && gDir > 0) { // continue up
      gCR = CR; gCF = CR + Box; Close[0] = CR; return 1;
    }
    if(gCR - Box*gReverse >= CF && gDir > 0) {
      gCF = CF; gCR = CF - Box; Close[0] = CF;
      gDir = -1; return 1;
    }
    break;
     
  case M_HIGHLOW:
    if(LF < gLF && gDir < 0) {
      gHR = LF - Box; gLF = LF;
      Close[0] = L; return 1;
    }
    if(gLF + Box*gReverse <= HR && gDir < 0) {
      gHR = HR; gLF = HR + Box;
      Close[0] = H; gDir = 1; return 1;
    }
    if(gHR < HR && gDir > 0) {
      gHR = HR; gLF = HR + Box;
      Close[0] = H; return 1;
    }
    if(gHR - Box*gReverse >= LF && gDir > 0) {
      gLF = LF; gHR = LF - Box;
      Close[0] = L; gDir = -1; return 1;
    }
    break;
     
  case M_CLOSE:
    if(CF < gCF && gDir < 0) { // continue down
      gCR = CF-Box; gCF = CF;
    return 1;
    }
    if(gCF+Box*gReverse <= CR && gDir < 0) { // go up
      gCR = CR; gCF = CR+Box;
      gDir = 1; return 1;
    }
    if(gCR < CR && gDir > 0) {
      gCR = CR; gCF = CR+Box;
    return 1;
    }
    if(gCR-Box*gReverse >= CF && gDir > 0) {
      gCF = CF; gCR = CF-Box;
      gDir = -1; return 1;
    }
    break;
  }
  return 4; // keep bar open, call again on next tick
}

The algorithm of the points-and-line chart in its 3 variants – POINTSIZE, HIGHLOW, and CLOSE – can be read up in the TASC article. Here I’ll only illustrate the usage of the bar() function for establishing special, event-driven bars instead of the usual time bars. The function evaluates the current and previous candle, modifies the current candle if needed, and returns 1 for beginning a new bar or 4 for continuing with the current bar. This way, all imaginable sorts of event driven bars can be generated, in the same way for backtests and for live trading. These bars are also displayed on the chart, and affect the scale of the X axis.

Since AmiBroker did not support standard indicators on a chart with special bars, the authors had exported the created chart and imported it as a fake asset price curve to Metastocks for using indicators with it. I wonder how this would work in live trading. Fortunately, the Zorro platform has no problems of this kind, since it treats standard bars and special bars in the same way. The run function looks just as usual:

function run()
{
  set(PLOTNOW,TICKS);
  BarPeriod = 1440;
  LookBack = 120;
  StartDate = 2017;
  EndDate = 2025;
  BarZone = EST;
  assetAdd("DJIA","STOOQ:^DJI");
  asset("DJIA");
  plot("MACD",MACDFix(seriesC(),14),NEW,RED);
  plot("Signal",rMACDSignal,0,GREY);
 }

The function uses STOOQ as a price source and plots the MACD indicator (red) and its signal line (grey). The chart below reproduces the DJIA chart in the TASC article, and applies a standard MACD:

Ein Bild, das Text, Diagramm, Reihe, Zahl enthält.

KI-generierte Inhalte können fehlerhaft sein.

Due to the variable length of bars, the time scale on the X axis is uneven; the DJIA moved a lot more in the years 2020 and 2022 than in the other years. The code can be downloaded from the 2025 script repository.

]]>
https://financial-hacker.com/the-points-and-line-chart/feed/ 2
A Better Stock Rotation System https://financial-hacker.com/a-better-stock-rotation-system/ https://financial-hacker.com/a-better-stock-rotation-system/#comments Sun, 27 Jul 2025 14:26:47 +0000 https://financial-hacker.com/?p=4861 Continue reading "A Better Stock Rotation System"]]> A stock rotation system is normally a safe haven, compared to other algorithmic systems. There’s no risk of losing all capital, and you can expect small but steady gains. The catch: Most of those systems, and also the ETFs derived from them, do not fare better than the stock index. Many fare even worse. But how can you make sure that your rotation strategy beats the index? There is a way.

In the TASC July 2026 issue, Markos Katsanos suggests a solution for a better stock rotation system. He applies two twists: Excluding the top performers, which often experience a reversal to the mean, and filtering out bear market situations. This improves stock rotation systems a lot.

The code of his system is unfortunately written in Amibroker languge, which means that for using it with any other platform, one must rewrite it from scratch. Amibroker does not use buy or sell orders. Instead it has a ‘portfolio rotation mode’ that is set up with many variables. Zorro’s C language has no special rotation mode, but uses buy and sell orders for rotating, just as for any other strategy. This requires rewriting the Amibroker code, but the positive side is that the script becomes much shorter and easier to comprehend.

var Score[1000],Weights[1000];

void run()
{
  StartDate = 2012;
  EndDate = 2025;
  BarPeriod = 1440;
  LookBack = 252; // 1 year

  Capital = slider(1,10000,0,20000,"Capital","");
  assetList("AssetsNASDAQ");
  assetAdd("QQQ","STOOQ:QQQ"); // for the bear market detection
  asset("QQQ");
// set up variables
  int MaxOpenPositions = 15;
  int ROCBars = 100;
  int ExitBars = 20;
  int MAPeriod = 300;
  int ExcludeTopN = 2;
// bear market filter
  var MAQQQ = ZMA(seriesC(),MAPeriod);
  bool Bear = MAQQQ < ref(MAQQQ,1);
  if(Day%ExitBars == 0) {
// assign a score to any asset
     for(listed_assets) {
       asset(Asset);
       if(Asset == "QQQ" || Bear)
         Score[Itor] = 0; // don't trade the index
       else
         Score[Itor] = ROC(seriesC(),ROCBars);
     }
// exclude the N top scores
     int i;
     for(i=0; i<ExcludeTopN; i++)
       Score[MaxIndex(Score,NumAssetsListed)] = 0;

// rotate the positions
    distribute(Weights,Score,NumAssetsListed,MaxOpenPositions,0.5);
     rotate(0); // decrease positions
     rotate(1); // increase positions
   }
}

We’re loading all NASDAQ stocks from an asset list (AssetsNASDAQ), and add the ‘QQQ’ index ETF because we’re needing that for the bear market filter. The MAQQQ variable holds the average index value, determined with a zero-lag moving average (ZMA). The ZMA has two advantages over a standard moving average: faster reaction (as the name says) and not needing a long data history. We assume a bear market when the average is falling.

Next, we check if we have reached the rotation date (Day%ExitBars is the number of days since start modulo the number of days for a rotation). If so, we loop over all assets and assign every one a score, depending on its N-day rate of return (ROC). The Itor variable is the number of the asset in the loop. The QQQ index gets no score, and in a bear market none of them gets a score.

Next, we remove the two top performers, since we assume they are overbought. The distribute function takes the scores and converts them to weights, while all weights sum up to 1. The function can be looked up in the Zorro manual (https://zorro-project.com/manual/en/renorm.htm). Finally we perform the actual rotation. This is a bit tricky, because we need two steps. The first step reduces all positions that ought to be reduced. The second step increases all positions that ought to be increased. This order is important, because if we increased a position first, the total volume could exceed our capital on the broker account.

The rotate function is not a Zorro function, but just assigns new position sizes to any asset of the portfolio:

void rotate(int Buy)
{
  for(listed_assets) {
    asset(Asset);
    int NewLots = Capital*Weights[Itor]/MarginCost;
    if(NewLots < LotsPool)
      exitLong(0,0,LotsPool-NewLots);
    else if(Buy && NewLots > LotsPool)
      enterLong(NewLots-LotsPool);
  }
}

Since I see from Markos Katsanos’ code that he optimized his variables, I have to do the same. For this, his variables now get optimization ranges:

set(PARAMETERS); // parameter optimization
setf(TrainMode,TRADES|GENETIC); // size matters
int MaxOpenPositions = optimize(15,5,30,5);
int ROCBars = optimize(100,50,250,50);
int ExitBars = optimize(20,10,50,5);
int MAPeriod = optimize(300,100,1000,100);
int ExcludeTopN = optimize(2,1,5,1);

We’re using genetic optimization with considering the trade volume (TRADES|GENETIC). The optimization takes about one minute. It’s in-sample, so take the result with a grain of salt. This is the equity curve resulting from a backtest:

In the backtest, we’re reinvesting profits; for this replace Capital with Equity in the rotate function. The blue bars are the account equity, the black line is the QQQ index. We can see that the account has far less drawdowns than the index. The black line in the small chart below is our trade volume, which is zero when a bear market is detected. The green line is the QQQ average, with bear market situations indicated in red. In 2022, the year when the Russian attack on Ukraine began, the system did not trade at all since the bear market filter was active almost the whole year.

The system produces 32% CAGR, with a 14% worst drawdown. This replicates Markos Katsanos’ results, but again, keep in mind that this is from an in-sample optimization. When applying walk-forward optimization (left as an exercise to the reader :), the CAGR goes down to 22%. Still a good performance, well beyond the NASDAQ index.

The code can be downloaded from the 2025 script repository.

]]>
https://financial-hacker.com/a-better-stock-rotation-system/feed/ 5
The Cybernetic Oscillator https://financial-hacker.com/the-cybernetic-oscillator/ https://financial-hacker.com/the-cybernetic-oscillator/#comments Sun, 18 May 2025 16:30:05 +0000 https://financial-hacker.com/?p=4847 Continue reading "The Cybernetic Oscillator"]]> Oscillator-type indicators swing around the zero line. They are often used for opening positions when oscillator exceeds a positive or negative threshold. In his article series about no-lag indicators, John Ehlers presents in the TASC June issue the Cybernetic Oscillator. It is built by applying a highpass and afterwards a lowpass filter to the price curve, then normalizing the result.

We already know Ehlers’ highpass filter from previous articles. It’s in the Zorro indicator library under the name HighPass3. The lowpass filter, Ehlers’ ‘SuperSmoother’, is also in the library, as well as the Sum of Sqares function for normalizing. This makes the Cybernetic Oscillator easy to code in C:

var CyberOsc(vars Data,int HPLength,int LPLength)
{
  vars HP = series(HighPass3(Data,HPLength));
  vars LP = series(Smooth(HP,LPLength));
  var RMS = sqrt(SumSq(LP,100)/100);
  return LP[0]/fix0(RMS);
}

We apply two Cybernetic Oscillators, one with a short and one with a long highpass cutoff, to an S&P500 chart from 2024:

void run()
{
  BarPeriod = 1440;
  LookBack = 250;
  StartDate = 20240301;
  EndDate = 20250407;
  asset("SPX500");
  plot("CyberOsc1",CyberOsc(seriesC(),30,20),NEW|LINE,RED);
  plot("CyberOsc2",CyberOsc(seriesC(),250,20),NEW|LINE,BLUE);
}

The resulting chart replicates Ehler’s chart in the article. The red line reproduces the swings of the price curve, the blue line the long-term trend:

Now we test the Cybernetic Oscillator by using it for a swing trading system. We open positions in the direction of the trend. The code, replicated from Ehlers’ EasyLanguage script:

void run()
{
  BarPeriod = 1440;
  LookBack = 250;
  StartDate = 2009;
  EndDate = 2025;
  Fill = 2; // enter at next open
  assetList("AssetsIB"); // simulate IBKR
  asset("SPY");
  vars LP = series(Smooth(seriesC(),20));
  vars BP1 = series(HighPass3(LP,55));
  var ROC1 = BP1[0] - BP1[2];
  vars BP2 = series(HighPass3(LP,156));
  var ROC2 = BP2[0] - BP2[2];

  if(!NumOpenLong && ROC1 > 0 && ROC2 > 0)
    enterLong();
  if(NumOpenLong && (ROC1 < 0 || ROC2 < 0))
    exitLong();
}

The system is opening a 1-share SPY position without reinvestment. We’re using commission, leverage and other trading parameters from a popular US broker for the simulation. Ehlers had produced his filter parameters with in-sample optimization, so take the result with a grain of salt. This is the equity curve:

Backtesting the same system with walk-forward optimization, for getting a more accurate result, is left as an exercise to the reader. Hint: you need to enter 4 extra lines. Ehlers’ system generates about 180 trades with 60% win rate and profit factor 2. The code can be downloaded from the 2025 script repository on https://financial-hacker.com.

 

]]>
https://financial-hacker.com/the-cybernetic-oscillator/feed/ 5
Trading the Channel https://financial-hacker.com/trading-the-channel/ https://financial-hacker.com/trading-the-channel/#comments Wed, 09 Apr 2025 12:14:55 +0000 https://financial-hacker.com/?p=4834 Continue reading "Trading the Channel"]]> One of the simplest form of trend trading opens positions when the price crosses its moving average, and closes or reverses them when the price crosses back. In the latest TASC issue, Perry Kaufman suggested an alternative. He is using a linear regression line with an upper and lower band for trend trading. Such a band indicator can be used to trigger long or short positions when the price crosses the upper or lower band, or when it gets close.

Let’s first code the bands. They are simply a regression line moved up or down so that it crosses through the highest and lowest price peaks and valleys. Here’s the piece of code in C:

var Slope = LinearRegSlope(seriesC(),N);
var Intercept = LinearRegIntercept(seriesC(),N);
var LinVal, HighDev = 0, LowDev = 0;
for(i=N; i>0; i--) {
  LinVal = Intercept + Slope*(N-i);
  HighDev = max(HighDev,priceC(i)-LinVal);
  LowDev = min(LowDev,priceC(i)-LinVal);
}

N is the number of bars for which the regression line is calculated. The line has the formula y = b + m*x, where b is the intercept and m the slope. The code generates both for the previous N bars, then calculates in the loop the maximum and minimum deviations (HighDev, LowDev). The regression value (LinVal) is calculated from the intercept and slope with the above formula. Since the bar offset i runs backwards from the current bar, the bar number that’s multiplied with the slope runs from N down to 0. Here’s the code applied to a SPY chart from 2025:

The candles can exceed the upper and lower bands because only the close price is used for the bands. It would probably improve the system, at least in theory, when we used the high and low prices instead.

Kaufman suggests several methods of trading with these bands; we’re here using the ‘Inside Channel’ method since it is, according to Kaufman, the most profitable. We open a long position when the price comes within a zone around the lower band, and close the position (or open a short position) when the price comes within a zone around the upper band. Here’s the complete trading system in C for Zorro, using the above code to calculate the bands.

void run()
{
  BarPeriod = 1440;
  StartDate = 20100101;
  LookBack = 150;
  assetList("AssetsIB");
  asset("SPY");
  if(is(LOOKBACK)) return;

  int i, N = 40;
  var Factor = 0.2;
  var Slope = LinearRegSlope(seriesC(),N);
  var Intercept = LinearRegIntercept(seriesC(),N);
  var LinVal, HighDev = 0, LowDev = 0;
  for(i=N; i>0; i--) {
    LinVal = Intercept + Slope*(N-i);
    HighDev = max(HighDev,priceC(i)-LinVal);
    LowDev = min(LowDev,priceC(i)-LinVal);
  }
  var Zone = Factor*(HighDev+LowDev);
  if(!NumOpenLong && priceC(0) < LinVal+LowDev+Zone)
    enterLong();
  if(!NumOpenShort && priceC(0) > LinVal+HighDev-Zone)
    exitLong();
}

We’ve selected the AssetsIB asset list, which contains the margins, commissions and other parameters from an US broker (IBKR). So the backtest simulates trading with IBKR. The resulting equity curve with the default parameters, N = 40 and Zone Factor = 20%, already shows promise with a 2.8 profit factor:

However, Kaufman mentioned that he tested N values from 20 to 150, and zone factors from 5% to 50%. We’ll do the same by optimizing these parameters. Of course a backtest with the best optimization result would be meaningless due to bias (see https://zorro-project.com/backtest.php). Therefore we’re using walk forward optimization for an out-of-sample backtest. Since anything with Zorro is done in code, we’ll insert 4 lines of C code for the optimization:

set(PARAMETERS); // optimize parameters
NumWFOCycles = 5;
N = optimize(40,20,150,10);
Factor = optimize(0.2,0.05,0.5,0.05);

We also changed the trading from only long positions to long and short by replacing exitLong with enterShort By default, entering a short position automatically exits the long one, and vice versa. So the system is always in the market with 1 share, either long or short. The walk forward optimization takes about 3 seconds. This is the resulting equity curve:

The profit factor rose to 7, with a 76% win rate – not bad for such a simple system. The code can be downloaded from the 2025 script repository on https://financial-hacker.com

]]>
https://financial-hacker.com/trading-the-channel/feed/ 8
Ehlers’ Ultimate Oscillator https://financial-hacker.com/ehlers-ultimate-oscillator/ https://financial-hacker.com/ehlers-ultimate-oscillator/#comments Mon, 17 Mar 2025 11:26:37 +0000 https://financial-hacker.com/?p=4823 Continue reading "Ehlers’ Ultimate Oscillator"]]> In his TASC article series about no-lag indicators, John Ehlers presented last month the Ultimate Oscillator.  What’s so ultimate about it? Unlike other oscillators, it is supposed to indicate the current market direction with almost no lag.

The Ultimate Oscillator is built from the difference of two highpass filters. The highpass function below is a straightforward conversion of Ehlers’ EasyLanguage code to C:

var HighPass3(vars Data,int Period)
{
  var a1 = exp(-1.414*PI / Period);
  var c2 = 2*a1*cos(1.414*PI / Period);
  var c3 = -a1*a1;
  var c1 = (1.+c2-c3) / 4;
  vars HP = series(0,3);
  return HP[0] = c1*(Data[0]-2*Data[1]+Data[2])+c2*HP[1]+c3*HP[2];
}

The function is named HighPass3 because we have already 3 other highpass filters in the Zorro indicator library, all with different code. The Ultimate Oscillator is the difference of two highpass filters with different periods, and scaled by its root mean square for converting the output to standard deviations. Fortunately Zorro has already a sum of squares function, which makes the code shorter and simpler than Ehlers’ original:

var UltimateOsc(vars Data,int Edge,int Width)
{
  vars Signals = series(HighPass3(Data,Width*Edge)-HighPass3(Data,Edge));
  var RMS = sqrt(SumSq(Signals,100)/100);
  return Signals[0]/fix0(RMS);
}

For checking its lag and smoothing power, we apply the Ultimate Oscillator to an S&P500 chart from 2024:

void run()
{
  BarPeriod = 1440;
  StartDate = 20240101;
  EndDate = 20241231;
  asset("SPX500");
  plot("UltOsc", UltimateOsc(seriesC(),20,2),LINE,RED);
}

The resulting chart replicates Ehler’s chart in the article. The Oscillator output is the red line:

We can see that the ultimate oscillator reproduces the market trend remarkably well and with no visible lag. The code can be downloaded from the 2025 script repository.

]]>
https://financial-hacker.com/ehlers-ultimate-oscillator/feed/ 7
Pimp your performance with key figures https://financial-hacker.com/pimp-your-performance-with-key-figures/ https://financial-hacker.com/pimp-your-performance-with-key-figures/#comments Wed, 08 Jan 2025 13:34:40 +0000 https://financial-hacker.com/?p=4782 Continue reading "Pimp your performance with key figures"]]> Not all scripts we’re hired to write are trading strategies. Some are for data analysis or event prediction – for instance: Write me a script that calculates the likeliness of a stock market crash tomorrow. Some time ago a client ordered a script for improving the performance of their company. This remarkable script was very different to a trading system. Its algorithm can in fact improve companies, but also your personal performance. How does this work?

Just like the performance of a stock, the performance of a company is measured by numerical indicators. They are named key figures. Key figures play a major role in quality management, such as the ISO 9000 standards. An ISO 9000 key figure is a detail indicator, such as the number of faults in a production line, the number of bugs found in a beta test, the number of new clients acquired per day, the total of positive minus negative online reviews, an so on. Aside from being essential for an ISO 9000 certification, these key figures have two purposes:

  • They give detailed insight and expose strengths and weaknesses.
  • And they give a strong motivation for reaching a certain goal.

The script below opens a user interface for entering various sorts of key figures. It calculates an overall score that reflects the current performance of a company – or of a person – and displays it in a chart. If you use it not for a company, but for yourself, it helps improving your personal life. And from the short script you can see how to create a relatively complex software with relatively few lines of code.

Hackers like the concept of key figures. They are plain numbers that you can work with. Anyone can define key figures for herself. If you’re a writer, an important key figure is the number of words you’ve written today; if you’re an alcolohic, it’s the number of drinks you had today. Many self-improvement books tell you precisely what you need to do for living a healthier, wealthier, happier life – but they all suffer from the same problem: long-term motivation. If you lack the iron will to keep your daily exercises, reduce smoking, stay away from fast food, and so on – all good resolutions will eventually fall into oblivion. If you had resolutions for 2025, you’ll soon know what I mean.

Failure is less likely when you can observe your progress any day and see its immediately effect on your overall performance score. This score is a direct measure of your success in live. Whether you’re a company or a person, you want to keep this core rising. This feedback produces a strong motivation, every day again.


The above performance chart is plotted by the key figure management script in C for the Zorro platform. The red line is the overall score, derived from all key figures in a way explained below. The blue line is the key figure for which you just entered a new value (in the example it’s the number of large or small features implemented in the last 6 months in the Zorro platform). The X axis is the date in YYMMDD format.

Of course, key figures can be very different. Some may have a daily goal, some not, some shall sum up over time, others (like your bank account value) are just taken as they are. The idea is that your overall score rises when you exceed the daily goals, and goes down otherwise. All key figures and their parameters can be freely defined in a CSV file, which can be edited with a text editor or with Excel. It looks like this:

Name, Decimals, Unit, Offset, Growth, Sum
Appr,1,0.1,0,0,0
Praise,0,10,0,-30,1
Weight,1,-0.1,-250,0.033,0
Duck,1,0.5,0,-0.5,1
Worth,0,1,-6000,0,0

In the first column you can assign a name. The second column is the number of decimals in the display, the third is their unit in the overall score, the fourth is an offset in the score, the fifth is the daily goal, and the last tells if the figures shall sum up over time (1) or not (0).

Example. Suppose you’re a president of a large country and want to pimp up your personal performance. What’s your key figures? First, of course, approval rate. Any tenth percent adds one point to your score. So the first entry is simple:

Name, Decimals, Unit, Offset, Growth, Sum
Appr, 1, 0.1, 0, 0, 0

Next, fame. Key figure is the daily number of praises on Fox News, OneAmerica, and Newsmax. You’ve ordered a White House department to count the praises; of course you’re personally counting them too, just in case. Less than 30 praises per day would be bad and reduce your score, more will improve it. So 30 praises are daily subtracted from your score. Any 10 further praises add one point. This is an accumulative key figure:

Praise, 0, 10, 0, -30, 1

Next, health. Key figure is weight. Your enemies spread rumors that you’re unfit and obese. Your doctors urge you to shed weight. So you want to lose one pound every month, which (you have your mathematicians for calculating difficult things) is about 0.033 per day. Any lost 0.1 pound adds one point to your score. The numbers are negative since you want your weight to go down, not up. The offset is your current weight.

Weight, 1, -0.1, -250, 0.033, 0

Next, literacy. Your enemies spread rumors you’re illiterate. To prove them wrong, you’ve decided to read at least half a page per day in a real book (you’ve chosen Duck for President to begin with). Any further half page adds one point to your score. This is also an accumulative figure.

Duck, 1, 0.5, 0, -0.5, 1

Finally, net worth. You’ve meanwhile learned to better avoid business attempts. Let your net worth grow due to the value increase of your inherited real estate, which is currently at 6 billion. Any million further growth adds one point to your score (numbers given in millions):

Worth, 0, 1, -6000, 0, 0

For improving your personal performance, download the script from the 2025 repository. Copy the files KeyFigures.csv and KeyFigures.c in your Strategy folder. Edit KeyFigures.csv for entering your personal key figures, as in the above example (you can later add or remove key figures and use Excel to add or remove the new columns to the data file). This is the script:

// Pimp Your Performance with Key Figures //////////////////////

string Rules = "Strategy\\KeyFigures.csv";
string Data = "Data\\KeyData.csv"; // key figures history
string Format = "0%d.%m.%Y,f1,f,f,f,f,f,f,f,f,f,f,f,f,f,f";
int Records,Fields;

var value(int Record,int Field,int Raw)
{
	var Units = dataVar(1,Field,2);
	var Offset = dataVar(1,Field,3);
	var Growth = dataVar(1,Field,4);
	var Value = 0;
	int i;
	for(i=0; i<=Record; i++) {
		if(dataVar(2,i,Field+1) < 0.) continue; // ignore negative entries
		Value += Growth;
		if(i == Record || (dataInt(1,Field,5)&1)) // Sum up? 
			Value += dataVar(2,i,Field+1)+Offset;
	}
	if(Raw) return Value-Offset;
	else return Value/Units;
}

var score(int Record)
{
	int i,Score = 0;
	for(i=0; i<Fields; i++) 
		Score += value(Record,i,0);
	panelSet(Record+1,Fields+1,sftoa(Score,0),YELLOW,16,4);
	return Score;
}


void click(int Row,int Col)
{
	dataSet(2,Row-1,Col,atof(panelGet(Row,Col)));
	score(Row-1);
	if(dataSaveCSV(2,Format,Data)) sound("Click.wav");
	int i;
	for(i=0; i<Records; i++) {
		var X = ymd(dataVar(2,i,0)) - 20000000;
		plotBar("Score",i,X,score(i),LINE,RED);
		plotBar(dataStr(1,Col-1,0),i,NIL,value(i,Col-1,1),AXIS2,BLUE);
	}
	if(Records >= 2) plotChart("");
}

void main()
{
	int i = 0, j = 0;
	printf("Today is %s",strdate("%A, %d.%m.%Y",NOW));
	ignore(62);
	PlotLabels = 5;
// File 1: Rules
	Fields = dataParse(1,"ssss,f1,f,f,f,i",Rules);
// File 2: Content
	Records = dataParse(2,Format,Data);
	int LastDate = dataVar(2,Records-1,0);
	int Today = wdate(NOW);
	if(LastDate < Today) { // no file or add new line
		dataAppendRow(2,16);
		for(i=1; i<=Fields; i++)
			if(!(dataInt(1,i-1,5)&1))
				dataSet(2,Records,i,dataVar(2,Records-1,i));
		Records++;
	}
	dataSet(2,Records-1,0,(var)Today);

// display in panel
	panel(Records+1,Fields+2,GREY,-58);
	panelFix(1,0);
	print(TO_PANEL,"Key Figures");
	for(i=0; i<Fields; i++)
		panelSet(0,i+1,dataStr(1,i,0),ColorPanel[0],16,1);
	panelSet(0,i+1,"Score",ColorPanel[0],16,1);
	panelSet(0,0,"Date",ColorPanel[0],16,1);
	for(j=0; j<Records; j++) {
		panelSet(j+1,0,strdate("%d.%m.%y",dataVar(2,j,0)),ColorPanel[0],0,1);
		score(j);
		for(i=0; i<Fields; i++) 
			panelSet(j+1,i+1,sftoa(dataVar(2,j,i+1),-dataVar(1,i,1)),ColorPanel[2],0,2);
	}
	panelSet(-1,0,"Rules",0,0,0);
}

The file locations and the CSV format of the key figures history are defined at the begin. The value function calculates the contribution of a particular key figure to the overall score. The score function updates the overall score. The click function, which is called when you enter a new value, calculates the score of that day, updates the spreadsheet, and prints the chart. The main function imports the data and key figures from their CSV files into datasets, prints the current day and displays a spreadsheet of your key figures and score history, like this:

Ein Bild, das Text, Screenshot, Schrift, Zahl enthält.

Automatisch generierte Beschreibung 

 
You will need Zorro S because the spreadsheet function is not available in the free version. Start the script any morning. It will open the spreadsheet, where you can click in any of the white fields and enter a new key figure value for today. You can anytime enter new figures for today or for past days. At any entry, the score is calculated and – if the history spans more than 2 days – a chart is plotted as in the above example.

Normally, personal performance depends on about 5-10 key figures (maximum is 15). For instance, miles you’ve jogged today, steps walked, exercises done, pages read, words written, words learned in a new language, value of your bank account, value of your stock portfolio, burgers eaten, cigarettes smoked, enemies killed, or number of old ladies you helped crossing the street. If you’re a president, consider the script a free present (we hope for generous tax exceptions in exchange). If you’re an ISO 9000 certified company and want to use the script for your quality management, please contact oP group to pay your fee. For personal use, the script is free. Pimp your performance and make the world a better place!

]]>
https://financial-hacker.com/pimp-your-performance-with-key-figures/feed/ 2
The Ultimate Strength Index https://financial-hacker.com/the-ultimate-strength-index/ https://financial-hacker.com/the-ultimate-strength-index/#comments Fri, 13 Dec 2024 09:47:55 +0000 https://financial-hacker.com/?p=4776 Continue reading "The Ultimate Strength Index"]]> The RSI (Relative Strength Index) is a popular indicator used in many trading systems for filters or triggers. In TASC 12/2024 John Ehlers proposed a replacement for this indicator. His USI (Ultimate Strength Index) has the advantage of symmetry – the range is -1 to 1 – and, especially important, less lag. So it can trigger trades earlier. Like the RSI, it enhances cycles and trends in the data, which makes it well suited for various sorts of trading systems. Let’s look how to realize it in code.

The USI is based on another indicator by John Ehlers, the Ultimate Smoother. It was covered in a previous article. Here’s again its code in C:

var UltimateSmoother (var *Data, int Length)
{
  var f = (1.414*PI) / Length;
  var a1 = exp(-f);
  var c2 = 2*a1*cos(f);
  var c3 = -a1*a1;
  var c1 = (1+c2-c3)/4;
  vars US = series(*Data,4);
  return US[0] = (1-c1)*Data[0] + (2*c1-c2)*Data[1] - (c1+c3)*Data[2] + c2*US[1] + c3*US[2];
}

Similar to the RSI, the Ultimate Strength Index calculates the normalized differences of the ups and downs in the price curve, after no-lag smoothing. Here’s Ehlers’ EasyLanguage code converted to C:

var UltimateStrength (int Length)
{
  vars SU = series(max(0,priceC(0) - priceC(1)));
  var USU = UltimateSmooth(series(SMA(SU,4)),Length);
  vars SD = series(max(0,priceC(1) - priceC(0)));
  var USD = UltimateSmooth(series(SMA(SD,4)),Length);
  return (USU-USD)/fix0(USU+USD);
}

The fix0 function is a convenience function that corrects possible divisions by zero. For checking the correctness of our conversion, we’re plotting the USI with two different time periods on a SPX chart. The code:

void run()
{
  BarPeriod = 1440;
  StartDate = 20230801;
  EndDate = 20240801;
  assetAdd("SPX","STOOQ:^SPX");
  asset("SPX");
  plot("USI(112)",UltimateStrength(112),NEW|LINE,BLUE);
  plot("USI(28)",UltimateStrength(28),NEW|LINE,BLUE);
}

The resulting chart replicates the ES chart in Ehlers’ article:

We can see that the long time frame USI enhances the begin and end of trends, the short time frame USI makes the cycles visible. The code can be downloaded from the 2024 script repository.

]]>
https://financial-hacker.com/the-ultimate-strength-index/feed/ 1
The Digital River Mystery https://financial-hacker.com/the-digital-river-mystery/ https://financial-hacker.com/the-digital-river-mystery/#comments Wed, 23 Oct 2024 10:57:47 +0000 https://financial-hacker.com/?p=4751 Continue reading "The Digital River Mystery"]]> Digital River was a popular eCommerce service, used by thousands of developers and software companies worldwide to distribute their software. oP group Germany, developers of the Zorro platform, also sold their licenses and subscriptions through Digital River. They have large clients, such as nVidia and Adobe. The first hint that something strange was going on with Digital River was an email in July to their clients.

They told that they will now charge hefty fees for using their store, for asking support questions, and for paying out. Mentioning the payout, they also told that they will from now on hold back payments for 2 months. Aside from the fact that they cannot legally change conditions in this way without their client’s consent, emails like that are normally business suicide. Since all competitors offer much better conditions, Digital  River would clearly sooner or later lose all their clients in this way. This email was a mystery. It made only sense under the assumption that Digital River intended to go out of business, but keep as much as their currernt client’s money as possible by offsetting them with insane fees.  

But Digital River did not hold back payments for 2 months. In November they stopped paying altogether. They did not give explanations. Reminders were answered with meaningless text blocks like this:

Please know that we are fully aware of the situation and are working diligently to resolve these issues as quickly as possible. Our team is investigating the cause of these delays to ensure they address them effectively and prevent such occurrences. We understand how important these payments are for you and your business. We are committed to ensuring that all outstanding payments are processed and that our communication with you is clear and timely moving forward. We appreciate your long-standing partnership and your trust in us over the years. Rest assured we are taking your concerns very seriously and trying to rectify this situation.

There is a lot of speculation about what is behind it. Some believe that Digital River ran in liquidity problems, and originally intended to still pay, although later and fewer. The legality of this is disputable, but it does not yet constitute obvious fraud. However, most clients now assume that they prepared an exit fraud with the intention to keep all their client’s sales. In that case the mysterious email had just the purpose to delay legal action.

On their website, Digital River gives no hint of any trouble, and continues to run the stores and charge the customer’s credit cards as if nothing happened. The only thing certain is that clients are now hurrying to switch to other eCommerce providers, such as PayPro Global or Paddle, and that their last three months earnings are possibly gone. Unless some part of if can be recovered by lawsuits or criminal proceedings.

Since most Zorro S license subscribers run their subscriptions through Digital River, oP group has activated a fallback server for verifying subscription tokens. So all subscriptions will remain valid until renewed with the new online store provider, PayPro Global. If you have recently purchased a product from Digital River, better cancel the charge on your credit card or request a refund. At least in some cases, it was reported that refunds were indeed paid.  

More details: Digital_river_runs_dry | Missing payments and evasive communication

]]>
https://financial-hacker.com/the-digital-river-mystery/feed/ 3