Doubts when Scaling In and Scaling Out

Hi,

I'm encountering some difficulties with the following code while implementing a scaling strategy for adjusting position sizes based on a market filter value (represented by "FLT_01_$SPX" in this example). The objective is to manage 25 positions according to the following rules:

  1. Value "0": Position size must be 4% for both open and new positions.
  2. Value "1": Position size must be 2% for both open and new positions.
  3. Value "2": Position size must be 2% for open positions; no new positions are allowed.
  4. Value "3": All open positions are sold; no new positions are allowed."

I need the code to scale in positions when the value changes from less than 0 to 0 and to scale out when the value changes from 0 to anything greater.

In the initial version, I only implemented scaling out.


// --- Settings --- 

SetOption("InitialEquity", 10000);
SetOption("MaxOpenshort", 0);
SetOption("MaxOpenLong", 25);
SetOption("MaxOpenPositions", 25);
SetOption("AllowPositionShrinking", True);
SetOption("MinShares", 0);
SetOption("HoldMinBars", 1);
SetOption("CommissionMode", 1);
SetOption("CommissionAmount", 0.2);


// --- Ranking ---

#include_once "Formulas\Norgate Data\Norgate Data Functions.afl"
WlNum = GetOption("FilterIncludeWatchlist"); 
List  = CategoryGetSymbols(categoryWatchlist, WlNum); 

if ( Status("stocknum") == 0 ) 
{ 
    StaticVarRemove("Value*");

    for (n = 0; (Symbol = StrExtract(List, n))  != "";  n++) 
    { 
        SetForeign (Symbol);
        FundValue = Foreign("FD_EVEBITDA_" + Symbol, "1", 2);
        FundScore = 1/(FundValue + 1e-9);
        Condition = NorgateIndexConstituentTimeSeries("$SP1500") AND FundValue > 0 AND FundScore > 0; 
        Score = IIf(Condition, FundScore, 0); 
        RestorePriceArrays(); 
        StaticVarSet ("Score" + Symbol, Score);  
    } 
    StaticVarGenerateRanks("Rank", "Score", 0, 1224 );
} 

Symbol   = Name(); 
Rank     = StaticVarGet("RankScore" + Symbol);


// --- Setup ---

Market  = Foreign("FLT_01_$SPX", "1");
Setup   = Market < 2;
Exit    = Market == 3;

DoScaleOut = Ref(Market, -1) == 0 AND Market  >  0;


// --- Trading ---

BuyCondition  = Setup AND Rank <= 25;
SellCondition = Rank > 25 OR Exit;

Buy  = BuyCondition;
Buy  = Buy + sigScaleOut * DoScaleOut;
Sell = SellCondition;

BuyPrice   = Open;
SellPrice  = Open; 

SetPositionSize(IIf(Ref(Market == 0, -1), 100, 50)/25, spsPercentOfEquity);
SetPositionSize(50, spsPercentOfPosition * (Buy == sigScaleOut));
SetTradeDelays(1, 1, 1, 1);

Apparently, it works as intended. The screenshot below shows how, after a value greater than 0 appears in the filter (upper chart), the positions scaled out.

However, I don’t understand why the current number of shares differs from the exited amount, given that the position scaling percentage is set to 50%.

image

In the second version, I added scaling in.


// --- Settings --- 

SetOption("InitialEquity", 10000);
SetOption("MaxOpenshort", 0);
SetOption("MaxOpenLong", 25);
SetOption("MaxOpenPositions", 25);
SetOption("AllowPositionShrinking", True);
SetOption("MinShares", 0);
SetOption("HoldMinBars", 1);
SetOption("CommissionMode", 1);
SetOption("CommissionAmount", 0.2);


// --- Ranking ---

#include_once "Formulas\Norgate Data\Norgate Data Functions.afl"
WlNum = GetOption("FilterIncludeWatchlist"); 
List  = CategoryGetSymbols(categoryWatchlist, WlNum); 

if ( Status("stocknum") == 0 ) 
{ 
    StaticVarRemove("Value*");

    for (n = 0; (Symbol = StrExtract(List, n))  != "";  n++) 
    { 
        SetForeign (Symbol);
        FundValue = Foreign("FD_EVEBITDA_" + Symbol, "1", 2);
        FundScore = 1/(FundValue + 1e-9);
        Condition = NorgateIndexConstituentTimeSeries("$SP1500") AND FundValue > 0 AND FundScore > 0; 
        Score = IIf(Condition, FundScore, 0); 
        RestorePriceArrays(); 
        StaticVarSet ("Score" + Symbol, Score);  
    } 
    StaticVarGenerateRanks("Rank", "Score", 0, 1224 );
} 

Symbol   = Name(); 
Rank     = StaticVarGet("RankScore" + Symbol);


// --- Setup ---

Market  = Foreign("FLT_01_$SPX", "1");
Setup   = Market < 2;
Exit    = Market == 3;

DoScaleOut = Ref(Market, -1) == 0 AND Market  >  0;
DoScaleIn  = Ref(Market, -1) > 0 AND Market == 0;


// --- Trading ---

BuyCondition  = Setup AND Rank <= 25;
SellCondition = Rank > 25 OR Exit;

Buy  = BuyCondition;
Buy  = Buy + sigScaleOut * DoScaleOut + sigScaleIn * DoScaleIN;
Sell = SellCondition;

BuyPrice   = Open;
SellPrice  = Open; 

SetPositionSize(IIf(Ref(Market == 0, -1), 100, 50)/25, spsPercentOfEquity);
SetPositionSize(50, spsPercentOfPosition * (Buy == sigScaleOut));
SetPositionSize(100, spsPercentOfPosition * (Buy == sigScaleIn));
SetTradeDelays(1, 1, 1, 1);

In this case, I don’t get a straightforward scaling in, even though there are many instances where the value changes from greater than 0 to 0."

I suspect there’s an error in my code, but I can’t pinpoint it. I would really appreciate your help with this

Best regards.

If you are using non zero trade delays your position size array would need shifting via Ref as scale on/out points must be in sync with with what you pass in position size.

Tomasz, thank you for your prompt answer and your time.

By shifting the position size, do you mean this?

tSetPositionSize(IIf(Ref(Market == 0, -1), 100, 50)/25, Ref(spsPercentOfEquity, -1));
SetPositionSize(50, Ref(spsPercentOfPosition, -1) * (Buy == sigScaleOut));

I must be doing something wrong anyway because I don't get the intended results. For example:

On 30/11/2021 it buys 58,99 shares of NRG

image

On 31/12/2021 there is no change

On 31/01/2022 it keeps 4.78 shares and sells 52.20 shares.

image

Besides that, why there is no scale in event when the condition for that is triggered?

1 Like

For example, you are using Buy == sigScaleOut. But then you delay Buy signal. So Buy == sigScaleOut condition occurs one bar BEFORE scale out because you delayed signals using SetTradeDelays. To avoid confusion, if you are checking conditions on Buy array and set position size accordingly it is advised to delay signals "by hand" instead of using SetTradeDelays.

SetTradeDelays(0, 0, 0, 0 );
Buy = Ref( Buy, -1 ); // delay signal "by hand" so you can be sure that trades actually happe
// when you think they happen
1 Like

I'll follow that approach, manually delaying signals. It feels more intuitive for setting each condition on the correct bar. However, something isn't working even before that stage. To help identify the bug in my code, I've simplified it to a single symbol, a date range from 01/2000 to 12/2000, and no delay at all.


// --- Settings --- 

SetOption("InitialEquity", 10000);
SetOption("MaxOpenshort", 0);
SetOption("MaxOpenLong", 1);
SetOption("MaxOpenPositions", 1);
SetOption("AllowPositionShrinking", True);
SetOption("MinShares", 0);
SetOption("HoldMinBars", 1);
SetOption("CommissionMode", 1);
SetOption("CommissionAmount", 0.2);


// --- Ranking ---

#include_once "Formulas\Norgate Data\Norgate Data Functions.afl"
WlNum = GetOption("FilterIncludeWatchlist"); 
List  = CategoryGetSymbols(categoryWatchlist, WlNum); 

if ( Status("stocknum") == 0 ) 
{ 
    StaticVarRemove("Value*");

    for (n = 0; (Symbol = StrExtract(List, n))  != "";  n++) 
    { 
        SetForeign (Symbol);
        FundValue = Foreign("FD_EVEBITDA_" + Symbol, "1", 2);
        FundScore = 1/(FundValue + 1e-9);
        Condition = NorgateIndexConstituentTimeSeries("$SP1500") AND FundValue > 0 AND FundScore > 0; 
        Score = IIf(Condition, FundScore, 0); 
        RestorePriceArrays(); 
        StaticVarSet ("Score" + Symbol, Score);  
    } 
    StaticVarGenerateRanks("Rank", "Score", 0, 1224 );
} 

Symbol   = Name(); 
Rank     = StaticVarGet("RankScore" + Symbol);


// --- Setup ---

Market  = Foreign("FLT_01_$SPX", "1");
Setup   = Market < 2;
Exit    = Market == 3;


// --- Trading ---

BuyCondition  = Setup AND Rank <= 25;
SellCondition = Rank > 25 OR Exit;

DoScaleOut = Ref(Market, -1) == 0 AND Market  >  0;
DoScaleIn  = Ref(Market, -1) > 0  AND Market ==  0;

Buy  = BuyCondition;
Buy  = Buy + sigScaleOut * DoScaleOut + sigScaleIn * DoScaleIn;

Sell = SellCondition;

BuyPrice   = Close;
SellPrice  = Close; 

SetPositionSize(IIf(Market == 0, 100, 50), Ref(spsPercentOfEquity, -1));
SetPositionSize(50,  spsPercentOfPosition * (Buy == sigScaleOut));
SetPositionSize(100, spsPercentOfPosition * (Buy == sigScaleIn));
SetTradeDelays(0, 0, 0, 0);


// --- Exploration ---

Filter = 1;

AddColumn(DoScaleOut, "Scale Out", 1.0);
AddColumn(DoScaleIn, "Scale In", 1.0);
AddColumn(PositionSize, "Position Size", 1.2);

Even though, there are some issues that whose cause I can't figure out.

1) Position Size: Why does it take a value of -1,050 on some bars? Based on the code, I would expect it to be either -100 or -50.

2) Scaling In: According to the exploration, there are two scaling in signals. However, the backtest doesn’t execute either of them.


3) Scaling Position: In the first scaling-out event, where 50% of the position size is being reduced, the current and exited share quantities are uneven. Now the position size refers to the same bar where the scaling occurs. Shouldn't the remaining and exited quantities be the same?

Thank you for your time and patience!

1 Like

You need to understand that SetPositionSize calculates value of position size and stores SINGLE VALUE into PositionSize array. Different ranges have different meanings. The range -100..0 means percent of equity. The range -1xxx to -1xxx where xxx is a number 0...999 means percent of position. So -1050 means 50 percent of position

1 Like

Tomasz, thank you for informing me about the Position Size array. In this case, it might be more practical to manage Position Size like this:

PositionSize = IIf(Buy == sigScaleOut, -1050, IIf(Buy == sigScaleIn, -1100, IIf(Market == 0, -100, -50)));

I've also modified the Buy statement as follows:

InTrade    = Flip(BuyCondition, SellCondition);
DoScaleOut = InTrade AND Ref(Market, -1) == 0 AND Market  >  0;
DoScaleIn  = InTrade AND Ref(Market, -1) > 0  AND Market ==  0;

Buy  = IIf(DoScaleOut, sigScaleOut, IIf(DoScaleIn, sigScaleIn, IIf(BuyCondition, 1, 0)));

With these two improvements, the code is now working as intended.

Here is the final version of the complete code:


// --- Settings --- 

SetOption("InitialEquity", 10000);
SetOption("MaxOpenshort", 0);
SetOption("MaxOpenLong", 1);
SetOption("MaxOpenPositions", 1);
SetOption("AllowPositionShrinking", True);
SetOption("AllowSameBarExit", True); 
SetOption("MinShares", 0);
SetOption("HoldMinBars", 0);
SetOption("CommissionMode", 1);
SetOption("CommissionAmount", 0.2);


// --- Ranking ---

#include_once "Formulas\Norgate Data\Norgate Data Functions.afl"
WlNum = GetOption("FilterIncludeWatchlist"); 
List  = CategoryGetSymbols(categoryWatchlist, WlNum); 

if ( Status("stocknum") == 0 ) 
{ 
    StaticVarRemove("Value*");

    for (n = 0; (Symbol = StrExtract(List, n))  != "";  n++) 
    { 
        SetForeign (Symbol);
        FundValue = Foreign("FD_EVEBITDA_" + Symbol, "1", 2);
        FundScore = 1/(FundValue + 1e-9);
        Condition = NorgateIndexConstituentTimeSeries("$SP1500") AND FundValue > 0 AND FundScore > 0; 
        Score = IIf(Condition, FundScore, 0); 
        RestorePriceArrays(); 
        StaticVarSet ("Score" + Symbol, Score);  
    } 
    StaticVarGenerateRanks("Rank", "Score", 0, 1224 );
} 

Symbol   = Name(); 
Rank     = StaticVarGet("RankScore" + Symbol);


// --- Setup ---

Market  = Foreign("FLT_01_$SPX", "1");
Setup   = Market < 2;
Exit    = Market == 3;


// --- Trading ---

BuyCondition  = Setup AND Rank <= 25;
SellCondition = Rank > 25 OR Exit;

InTrade    = Flip(BuyCondition, SellCondition);
DoScaleOut = InTrade AND Ref(Market, -1) == 0 AND Market  >  0;
DoScaleIn  = InTrade AND Ref(Market, -1) > 0  AND Market ==  0;

Buy  = IIf(DoScaleOut, sigScaleOut, IIf(DoScaleIn, sigScaleIn, IIf(BuyCondition, 1, 0)));
Sell = SellCondition;

PositionSize = IIf(Buy == sigScaleOut, -1050, IIf(Buy == sigScaleIn, -1100, IIf(Market == 0, -100, -50)));

BuyPrice   = Close;
SellPrice  = Close; 

SetTradeDelays(0, 0, 0, 0);


// --- Exploration ---

Filter = 1;

AddColumn(InTrade, "In Trade", 1.0);
AddColumn(DoScaleOut, "Scale Out", 1.0);
AddColumn(DoScaleIn, "Scale In", 1.0);
AddColumn(PositionSize, "Position Size", 1.2);

Regards.

2 Likes