Traders' Tip text
The Money Flow Oscillator (MFO) created by Vitali Apirine measures buying and selling pressure over a specific lookback. Bullish divergence indicates less selling pressure and bearish divergence indicates less buying pressure. Signals can be generated by looking for divergences of MFO with price and centerline crossovers. For our example code, we will combine both ideas into a counter-trend long-only trading system.
To identify divergences between price and oscillator, we'll be applying a straightforward approach. A divergence is detected when the SRSI indicator fails to confirm a price extreme, that is, the highest high of 20 days for bearish divergence or the 20-day lowest low for bullish divergence. This technique improves divergence detection time, practically reducing delay to a minimum compared to finding retracements from recent peaks or troughs.
System rules- Once a bullish divergence is detected, enter long next bar at open if MFO is below its centerline
- Exit long next bar at open when MFO crosses above the centerline
Trades from the short side are deliberately not taken as their performance seems poor.
Figure 1. Bullish divergence between the MFO and price formed in June 2015 triggered a long trade in KO (Coca Cola).After updating the TASCIndicators library to v2015.09 or later, the MoneyFlowOscillator indicator can be found under the TASC Magazine Indicators group. You can plot it on a chart or use it as an entry or exit condition in a Rule-based Strategy without having to program a line of code yourself.
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using WealthLab;
using WealthLab.Indicators;
using TASCIndicators;
namespace WealthLab.Strategies
{
/*
MFO divergence:
Price sets a lowest low but the indicator fails to confirm the new low and turns up
*/
public class MFO_Divergence : WealthScript
{
private StrategyParameter paramHighest;
private StrategyParameter paramPeriod;
public MFO_Divergence()
{
paramPeriod = CreateParameter("MFO period", 20, 2, 100, 1);
paramHighest = CreateParameter("Highest high of", 20, 5, 50, 1);
}
protected override void Execute()
{
bool peak = false; int peakBar = -1;
int high = paramHighest.ValueInt;
bool trough = false; int troughBar = -1;
int low = paramHighest.ValueInt;
int period = paramPeriod.ValueInt;
MoneyFlowOscillator mfo = MoneyFlowOscillator.Series( Bars, period );
Lowest indicatorLowest = Lowest.Series( mfo, low );
Lowest hLow = Lowest.Series( Low, low );
HideVolume(); LineStyle solid = LineStyle.Solid;
ChartPane mfoPane = CreatePane( 50, false, true );
PlotSeries( mfoPane, mfo, Color.Green, solid, 2 );
DrawHorzLine( mfoPane,0,Color.Blue,LineStyle.Dashed,1);
for(int bar = GetTradingLoopStartBar(period); bar < Bars.Count; bar++)
{
if (!IsLastPositionActive)
{
/* 1st peak: both price and indicator */
if( peak == false )
{
if( ( High[bar-1] == Highest.Series( High, high )[bar-1] )
& ( mfo[bar-1] == Highest.Series( mfo, high )[bar-1] )
& TurnDown( bar, High ) & TurnDown( bar, mfo ) )
{
peak = true; peakBar = bar-1;
}
}
if( peak == true )
{
if( ( High[bar] != Highest.Series( High, high )[bar] )
& ( mfo[bar] == Highest.Series( mfo, high )[bar] ) )
peak = false;
}
/* 2nd peak: price high not confirmed by the indicator */
if( peak == true )
{
if( ( High[bar-1] == Highest.Series( High, high )[bar-1] )
& ( High[bar-1] >= High[peakBar] )
& ( mfo[bar-1] != Highest.Series( mfo, high )[bar-1] )
& ( mfo[bar-1] < mfo[peakBar] ) &
TurnDown( bar, High ) & TurnDown( bar, mfo ) )
{
peak = false;
/* Shorting doesn't work well */
// Fade the trend
// if( mfo[bar] > 0 )
// if( ShortAtMarket( bar+1 ) != null )
// LastPosition.Priority = Close[bar];
/* Highlight divergence */
for (int b = peakBar; b <= bar; b++)
SetPaneBackgroundColor( mfoPane, b, Color.FromArgb( 30, Color.LightCoral ) );
DrawLine( PricePane, peakBar, High[peakBar], bar-1, High[bar-1], Color.Red, solid, 2 );
DrawLine( mfoPane, peakBar, mfo[peakBar], bar-1, mfo[bar-1], Color.Blue, solid, 2 );
}
}
/* 1st trough: both price and indicator */
if( trough == false )
{
if( ( Low[bar-1] == Lowest.Series( Low, low )[bar-1] )
& ( mfo[bar-1] == Lowest.Series( mfo, low )[bar-1] )
& TurnUp( bar, Low ) & TurnUp( bar, mfo ) )
{
trough = true; troughBar = bar-1;
}
}
if( trough == true )
{
if( ( Low[bar] != Lowest.Series( Low, low )[bar] )
& ( mfo[bar] == Lowest.Series( mfo, low )[bar] ) )
trough = false;
}
/* 2nd trough: price low not confirmed by the indicator */
if( trough == true )
{
if( ( Low[bar-1] == Lowest.Series( Low, low )[bar-1] )
& ( Low[bar-1] <= Low[troughBar] )
& ( mfo[bar-1] != Lowest.Series( mfo, low )[bar-1] )
& ( mfo[bar-1] > mfo[troughBar] ) &
TurnUp( bar, Low ) & TurnUp( bar, mfo ) )
{
trough = false;
/* Fade the trend */
if( mfo[bar] < 0 )
if( BuyAtMarket( bar+1 ) != null )
LastPosition.Priority = -Close[bar];
/* Highlight divergence */
for (int b = troughBar; b <= bar; b++)
SetPaneBackgroundColor( mfoPane, b,
Color.FromArgb( 30, Color.LightGreen ) );
DrawLine( PricePane, troughBar, Low[troughBar],
bar-1, Low[bar-1], Color.Blue, solid, 2 );
DrawLine( mfoPane, troughBar, mfo[troughBar],
bar-1, mfo[bar-1], Color.Red, solid, 2 );
}
}
} else
{
Position p = LastPosition;
if( p.PositionType == PositionType.Long )
{
if( CrossOver(bar, mfo, 0) )
ExitAtMarket( bar+1, p, "MFO Crossover" );
}
else
if( CrossUnder(bar, mfo, 0) )
ExitAtMarket( bar+1, p, "MFO Crossunder" );
}
}
}
}
}
Eugene
Wealth-Lab team
www.wealth-lab.com