Creating synthetic data for leveraged ETFs


#1

I wanted to test a strategy using leveraged ETFs but most of them have only been in existence for a few years. There wasn't enough data for proper backtesting. I found this site where JW from Trendxplorer had posted a really nice AFL that does just that:

Thank you so much JW!
http://forum.amibroker.com/badges/2/member?username=trendxplorer


#2

The code from the link above is great for creating synthetic data, however it will only create the close prices. I'd like to get it to create the open, high, low and close prices. I've made an amateur's attempt to modify the code. It will give the correct close prices but not the correct open high or low prices. Does anyone have any suggestions how I might be able to do it? Below is a copy of the original code and below that is my modified code:

Original Code (Creates synthetic close prices only):

// --- ATC_SyntheticSymbol.afl ---
//
// --- colophon ---
//
// code by TrendXplorer
// trendxplorer@gmail.com
// www.trendxplorer.info
// version: December 31, 2014
//
// --- instructions for usage ---
//
// 1. Set name and leverage for composite ticker on parameters setting for analysis. For SDS, Leveraged Source= SDS, Name Target Symbol = ~SDS, Leverage Factor = 1, Invert Price Data = Yes
// 2. Run scan on ticker with longest price history: base ticker, i.e. SPY, use daily setting
// 3. On Analysis, adjust Apply to *Current and Range: All quotes. Finally to create the ETF, hit Scan and the symbol is created in a blink of an eye.
//
// -------------------------------

// --- inputs ---
_levSource      = ParamStr( "Leveraged Source:"   , "SSO"                  );
_targetSymbol   = ParamStr( "Name Target Symbol:" , "$"                    );
leverage        = Param   ( "Leverage Factor:"    , 2.0, -3.0, 3.0, 0.0001 );
invert          = ParamToggle( "Invert Price Data", "No|Yes", 0            );

// --- calculations ---
invert          = 1 - invert * 2; // value for "No": 1, value for "Yes: -1
baseChange      = -1 + Close / Ref ( Close, -1 );
targetChange    =  1 + baseChange * leverage * invert;
levData         =  Foreign( _levSource, "C" );

// --- AddToComposite statements are for Analysis -> Scan ---
if ( Status("action") == actionScan )
{
	for ( bar = BarCount; bar > 0; bar-- )  // from most recent bar to first bar of base symbol
	{
		if ( NOT IsNull( levData[bar-1] ) )
			targetData[bar-1] = levData[bar-1];
		else
			targetData[bar-1] = targetData[bar] / targetChange[bar];
	}
	AddToComposite( targetData, _targetSymbol, "X", atcFlagDefaults );
	Buy = 0;  // required by scan mode
}
SetOption("RefreshWhenCompleted", True);

// --- generate columns for strategy exploration ---
AddColumn( Close                     , "Close"        , 3.3 );
AddColumn( baseChange * 100          , "BaseChange%"  , 3.3 );
AddColumn( ( targetChange - 1 ) * 100, "TargetChange%", 3.3 );
AddColumn( levData                   , "OriginalData" , 3.3 );
if ( Status( "action" ) == actionExplore ) SetSortColumns( -2 );
Filter = 1;

// --- end of code ---

Modified Code (trying to get it to create Open, High, Low and Close prices):

_levSource      = ParamStr( "Leveraged Source:"   , "SSO"                  );
_targetSymbol   = ParamStr( "Name Target Symbol:" , "$"                    );
leverage        = Param   ( "Leverage Factor:"    , 2.0, -3.0, 3.0, 0.0001 );
invert          = ParamToggle( "Invert Price Data", "No|Yes", 0            );

// --- calculations ---
invert          = 1 - invert * 2; // value for "No": 1, value for "Yes: -1
baseChangeO      = -1 + Open / Ref ( Open, -1 );
baseChangeH      = -1 + High / Ref ( High, -1 );
baseChangeL      = -1 + Low / Ref ( Low, -1 );
baseChangeC      = -1 + Close / Ref ( Close, -1 );
targetChangeO    =  1 + baseChangeO * leverage * invert;
targetChangeH    =  1 + baseChangeH * leverage * invert;
targetChangeL    =  1 + baseChangeL * leverage * invert;
targetChangeC    =  1 + baseChangeC * leverage * invert;
levDataO         =  Foreign( _levSource, "O" );
levDataH         =  Foreign( _levSource, "H" );
levDataL         =  Foreign( _levSource, "L" );
levDataC         =  Foreign( _levSource, "C" );

// --- AddToComposite statements are for Analysis -> Scan ---
if ( Status("action") == actionScan )
{
	for ( bar = BarCount; bar > 0; bar-- )  // from most recent bar to first bar of base symbol
	{
		if ( NOT IsNull( levDataO[bar-1] ) )
			targetDataO[bar-1] = levDataO[bar-1];
		else
			targetDataO[bar-1] = targetDataO[bar] / targetChangeO[bar];
			
		if ( NOT IsNull( levDataH[bar-1] ) )
			targetDataH[bar-1] = levDataH[bar-1];
		else	
			targetDataH[bar-1] = targetDataH[bar] / targetChangeH[bar];
			
		if ( NOT IsNull( levDataL[bar-1] ) )
			targetDataL[bar-1] = levDataL[bar-1];
		else
			targetDataL[bar-1] = targetDataL[bar] / targetChangeL[bar];
			
		if ( NOT IsNull( levDataC[bar-1] ) )
			targetDataC[bar-1] = levDataC[bar-1];
		else
			targetDataC[bar-1] = targetDataC[bar] / targetChangeC[bar];
	}
	AddToComposite( targetDataO, _targetSymbol, "O", atcFlagDefaults );
	AddToComposite( targetDataH, _targetSymbol, "H", atcFlagDefaults );
	AddToComposite( targetDataL, _targetSymbol, "L", atcFlagDefaults );
	AddToComposite( targetDataC, _targetSymbol, "C", atcFlagDefaults );
	Buy = 0;  // required by scan mode
}
SetOption("RefreshWhenCompleted", True);

#3

Hello Marcel
i read the original code and i can see that line

	AddToComposite( targetData, _targetSymbol, "X", atcFlagDefaults );

so if you read the manual says that "X" - updates all OHLC fields at once

field codes: "C" - close , "O" - open, "H" - high, "L" - low, "V" - volume, "I" - open interest, "1" - Aux1 field, "2" - Aux2 field,
"X" - updates all OHLC fields at once

I didn’t run or test the original code but for me has exactly what you need and you DONOT need to modify anything according your question


#4

Thank you so much @PanoS. It does put data in all the fields, but it puts the same close data in each field so that the Close=Open=High=Low. I'd like it to put the different respective values in the OHLC spots.


#5

Hello
Just came back of work and I try to run your Modified code.
In my database I donot have "SSO" so I had to put other ticker (^GDAXI)

_levSource = ParamStr("Leveraged Source: " , "^GDAXI");

Our next step BEFORE run Scan….. ALLWAYS go to Analysis Parameters ( in my case I had to press the Button “Reset All” then I run the Scan.)

Magic… :grinning: :grinning: :grinning:
I did run your Modified code in One ticker and also in watchlist and I DIDNOT see any problem all O,H,L,C was ok.

have a nice time.


#6

Yes, with the modified code I can get the proper OHLC up until the last bar available of the original ETF but before that it won't work. (around 2008 for SPXL) The code is basically just copying the original SPXL data back to 2008 and putting it into the synthetic composite symbol. I did some studying about it today and I realized that if you calculate the OHLC independent of each other, they'll wander from each other and you'll end up with the high lower than the open etc. You have to do something like extrapolate just the close price and then calculate the open, high and low from the extrapolated close.

I looked all over the net for any kind of calculation for leveraged ETFs and just found some very vague information. The ETF companies say that for example with 3X leverage, they try to come close to 3X the daily change of the associated index with futures etc but it isn't exactly 3X. So I couldn't find any mathematical formulae. There is also of course daily rebalancing which in most cases means over the long term, you'll lose money with a leveraged ETF.