Multi-Agents and the Power of the Series Function

Jeff Swanson wrote a great post on multi-agent trading a few years ago.

Jeff created a simple mean reversion system and then created two derivatives that culminated in three systems (or three agents.)  Using Murray Ruggiero’s Equity Curve Feedback, he was able to poll which system was doing the best, synthetically, and execute the strategy that showed the best performance.   If memory serves, picking the highflyer turned out to be the way to go.  Jeff had just touched the surface of Murray’s tool. but it definitely did the job.  Murray contracted me to fix some problems with the ECF tool and I did, but the tool is just way too cumbersome, resource hungry and requires a somewhat higher level of EasyLanguage knowledge to be universally applicable.  I was doing similar research in the area of polling multiple strategies and picking the best, just like Jeff did, and just executing that one system, when I thought about this post.  Traders do this all the time.  They have multiple strategies in the pipeline and monitor the performance and if one is head and shoulders better than what they are currently trading, they will switch systems.  This was one of the side benefits of the ECF tool.

What is an agent

An agent is any trading system that produces a positive expectancy.  Using multiple agents in a polling process allows a trader to go with the strategy that is currently performing the best.  This sounds reasonable, but there are pitfalls.  You could always be behind the curve – picking the best system right before it has its draw down.  Agents can be similar, or they can be totally different types of systems.  I am going to follow in Jeff’s footsteps and create three agents with the same DNA.  Here is what I call the AgentSpawner strategy.

inputs: movAvgLen(200),numDownDays(3),numDaysInTrade(2),stopLoss(5000);


value1 = countIf(c < c[1],numDownDays);
if c > average(c,movAvgLen) and value1 = numDownDays then
buy next bar at open;
if barsSinceEntry = numDaysInTrade then
sell next bar at open;
if marketPosition = 1 then sell next bar at entryPrice - stopLoss/bigPointValue stop;
Use this template and optimize inputs to spawn new agents

This code trades in the direction of the longer-term moving average and waits for a pull back on N consecutive down closes.  I am using the neat function countIF. This function counts the number of times the conditional test occurs in the last N bars.  If I want to know the number of times I have had a down close in the last 3 days, I can use this function like this.

Value1 = countIF(c<c[1],3);

// this is what the function doues
// 1.) todays close < yesterdays close + 1
// 2.) yesterdays close < prior days close + 2
// 3.) day before yesterdays close < prior cays close + 3

// If value3 = 3 then I know I had three conscecutive down
// closes. If value3 is less than three then I did not.

If the close is greater than the longer-term moving average and I have N consecutive down closings, then I buy the next bar at the open.  I use a wide protective stop and get out after X bars since entry.  Remember EasyLanguage does not count the day of entry in its barsSinceEntry calculation.  I am not using the built-in setStopLoss as I don’t want to get stopped out on the day of entry.  In real trading, you may want to do this, but for testing purposes my tracking algorithm was not this sophisticated.  I spawned three agents with the following properties.

	Case 1: //Agent 1
movAvgLen = 200;
numDownDays = 2;
numDaysInTrade = 15;
stopLoss = 7500;
Case 2: //Agent 2
movAvgLen = 140;
numDownDays = 3;
numDaysInTrade = 9;
stopLoss = 2500;
Case 3: //Agent 3
movAvgLen = 160;
numDownDays = 3;
numDaysInTrade = 15;
stopLoss = 2500;

System Tracking Algorithm

This is why I love copy-paste programming.  This can be difficult if you don’t know your EasyLanguage or how TradeStation processes the bars of data.  Get educated by checking my books out at amazon.com – that is if you have not already.  This code is a very simplistic approach for keeping track of a system’s trades and its equity.

value4 = countIf(c<c[1],4);
value3 = countIf(c<c[1],3);
value2 = countIf(c<c[1],2);
//Agent #1 tracking algorithm
if sys1Signal<> 1 and c[1] > average(c[1],160) and value2[1] = 2 then
begin
sys1Signal = 1;
sys1BarCount = -1;
sys1TradePrice = open;
sys1LExit = open - 7500/bigPointValue;
end;
if sys1Signal = 1 then
begin
sys1BarCount+=1;
if low < sys1LExit and sys1BarCount > 0 then
begin
sys1TradePrice = sys1LExit;
sys1Signal = 0;
end;
if sys1BarCount = 16 and sys1Signal = 1 then
begin
sys1TradePrice = open;
sys1Signal = 0;
end;
end;
Yes, this looks a little hairy, but it really is simple stuff

I am pretending to be TradeStation here.  First, I need to test to see if Agent#1 entered into a long position.  If the close of yesterday is greater than the moving average, inclusive of yesterdays close, and there has been two consecutive down closes, then I know a trade should have been entered on todays open.  EasyLanguage’s next bar paardigm cannot be utilized here.  Remember I am not generating signals, I am just seeing if today’s (not tomorrows or the next bars) trading action triggered a signal and if so, I need to determine the entry/exit price.  I am gathering this information so I can feed it into a series function.  If a trade is triggered, I set four variables:

  1. sys1Signal – 1 for long, -1 for short, and 0 for flat.
  2. sys1BarCount – set to a -1 because I immediately increment.
  3. sys1TradePrice – at what price did I enter or exit
  4. sys1LExit – set this to our stop loss level

If I am theoretically long, remember we are just tracking here, then I need to test, starting with the following day, if the low of the day is below our stop loss level and if it is I need to reset two variables:

  1. sys1TradePrice – where did I get out
  2. sys1Signal – set to 0 for a flat position

If not stopped out, then I start counting the number of bars sys1Signal is equal to 1.  If sys1BarCount = 16, then I get out at the open by resetting the following variables:

  1. sys1TradePrice = open
  2. sys1Signal = 0

If you look back at the properties for Agent#1 you will see I get out after 15 days, not 16.  Here is where the next bar paradigm can make it confusing.  The AgentSpawner strategy says to sell next bar at open when barsSinceEntry = 15.  The next bar after 15 is 16, so we store the open of the 16th bar as our trade price.

Now copy and paste the code into a nice editor such as NotePad++ or NotePad and replace the string sys1 with sys2.  Copy the code from NotePad++ into your EasyLanguage editor.  Now back to NotePad++ and replace sys2 with sys3.  Copy that code into the EL edition too.  Now all you need to do is change the different properties for each agent and you will have three tracking modules.

The Power of the EasyLanguage Series Function

The vanilla version of EasyLanguage has object-oriented nuances that you may not see right off the bat.  In my opinion, a series function is like a class.  Before I get started, let me explain what I mean by series.  All EasyLanguage function are of three types.

  1. simple – like a Bollinger band calculation
  2. series – like we are talking about here
  3. auto-detect – the interpreter/compiler decides

The series function has a memory for the variables that are used within the function.  Take a look at this.

input: funcID(string),seed(numericSimple);

vars: count(0);
if barNumber = 1 then // on first bar seed count
count = seed;
count = count+1;
print(d," ",funcID," ", count);
SeriesFunctionTest = count;
Count is class-like member

On the first bar of the function call – remember it will be called on each bar in the chart, the function variable count is assigned seed. Seed will be ignored on subsequent bars.   What makes this magical is that no matter how many times you call the function on the same bar it remembers the internal variables on somewhat of a hierarchical basis (each call remembers its own stuff.)  It like a class in that it gets instantiated on the very first call.  Meaning if you call it three times on the first bar of the data, you will have three distinct internal variable memories.  Take a look at my sandbox function driver and its output.

result = SeriesFunctionTest("Call #1",50);
result = SeriesFunctionTest("Call #2",5);
result = SeriesFunctionTest("Call #3",100);

//outPut

1170407.00 Call #1 51.00 //first bar 51 = seed + count + 1
1170407.00 Call #2 6.00 //first bar 6 = seed + count + 1
1170407.00 Call #3 101.00 //first bar 101 = seed + count + 1

1170410.00 Call #1 52.00 // second bar it remembered count was 51
1170410.00 Call #2 7.00 // second bar it remembered count was 6
1170410.00 Call #3 102.00 // second bar it remembered count was 101

1170411.00 Call #1 53.00 // you have a unique function values that
1170411.00 Call #2 8.00 // were instantiated on the first bar
1170411.00 Call #3 103.00 // of the test.

1170412.00 Call #1 54.00
1170412.00 Call #2 9.00
1170412.00 Call #3 104.00
Series functions rock - but they are resource hungry

Why is this important?

I have created a PLSimulator function that keeps track of the three agent’s performance.  I need the profit or loss to stick with each function and then also add or subtract from it.  This is a neat function.  Remember if you like this stuff buy my books at Amazon.com.

//ProfitLoss Simulator
Inputs: signal(numericseries),tradePrice(numericSimple),orderType(numericSimple),useOte(Truefalse);
Vars:dmode(0),LEPrice(-99999),LXPrice(-99999),SEPrice(-99999),SXPrice(-99999);
vars: GProfit(0),OpenProfit(0);
vars: modTradePrice(0);

vars: printOutTrades(True);

modTradePrice = tradePrice;

if orderType = 1 then // stop order
begin
if signal = 1 or (signal = 0 and signal[1] = - 1) then
modTradePrice = maxList(open,modTradePrice);
if signal = -1 or (signal = 0 and signal[1] = 1) then
modTradePrice = minList(open,modTradePrice);
end;

if orderType = 2 then // limit order
begin
if signal = 1 or (signal = 0 and signal[1] = - 1) then
modTradePrice = minList(open,modTradePrice);
if signal = -1 or (signal = 0 and signal[1] = 1) then
modTradePrice = maxList(open,modTradePrice);
end;
if orderType = 3 then // market order
begin
modTradePrice = open;
end;

If Signal[0]=1 And (Signal[1]=-1 Or Signal[1]=0) Then
begin
LEPrice=modTradePrice;
SXPrice = -999999;
condition1 = false;
If Signal[1]=-1 Then
begin
SXPrice=modTradePrice;
GProfit=(SEPrice-SXPrice)+GProfit;
condition1 = True;
End;
if not(condition1) then
if printOutTrades then Print(d," L:Entry ",LEPrice)
else
if printOutTrades then Print(d," L:Entry ",LEPrice," ",(SEPrice-SXPrice)*bigPointValue:8:2," ",GProfit*bigPointValue:9:2);
End;
{('***********************************************}
If Signal[0]=-1 And (Signal[1]=1 Or Signal[1]=0) Then
begin
SEPrice=modTradePrice;
LXPrice = 999999;
condition1 = false;
If Signal[1]=1 Then
begin
condition1 = True;
LXPrice=modTradePrice;
GProfit=(LXPrice-LEPrice)+GProfit;
End;
if not(condition1) then
if printOutTrades then Print(d," S:Entry ",SEPrice)
else
if printOutTrades then Print(d," L:Exit ",LXPrice," ",(LXPrice-LEPrice)*bigPointValue:8:2," ",GProfit*bigPointValue:9:2);

End;
If Signal[0]=0 And Signal[1]=-1 Then
begin
SXPrice = modTradePrice;
GProfit=(SEPrice-SXPrice)+GProfit;
if printOutTrades then Print(d," S:Exit ",SXPrice," ",(SEPrice-SXPrice)*bigPointValue:8:2," ",GProfit*bigPointValue:9:2);

end;
If Signal[0]=0 And Signal[1]=1 Then
begin
LXPrice = modTradePrice;
GProfit=(LXPrice-LEPrice)+GProfit;
if printOutTrades then Print(d," L:Exit ",LXPrice," ",(LXPrice-LEPrice)*bigPointValue:8:2," ",GProfit*bigPointValue:9:2);

end;

If Signal[1]=1 And useOte=True Then
begin
OpenProfit=(Close[1]-LEPrice);
End;
If Signal[1]=-1 and useOte=True Then
begin
OpenProfit=(SEPrice-Close[1]);
End;
If Signal[1]=0 Or useOte=False Then
begin
OpenProfit=0;
End;

PLSimulator=(GProfit+OpenProfit)*bigpointvalue;
Simulate profit and loss and more importantly keep track of it

Feed tracker algorithm data into the function

Your information must be properly assigned to get this to work.  First, I show how to get the information into the function.  The function does all the work and returns the equity.  I then determine the best agent by looking at the ROC over the past thirty days of equity for each agent and pick the very best.  I then trade the very best.  This is a very quick application of the function.  I will have a more sophisticated function, something akin to Murray’s ECF but with much less overhead and more strategy templates.

sys1Equity = PLSimulator(sys1Signal,sys1TradePrice,1,True);
sys2Equity = PLSimulator(sys2Signal,sys2TradePrice,1,True);
sys3Equity = PLSimulator(sys3Signal,sys3TradePrice,1,True);


vars: multiAgent(0);

value1 = maxList(sys1Equity-sys1Equity[30],sys2Equity-sys2Equity[30],sys3Equity-sys3Equity[30]);
multiAgent = 1;
if sys2Equity-sys2Equity[30] = value1 then multiAgent = 2;
if sys3Equity-sys3Equity[30] = value1 then multiAgent = 3;


{print(d," ",sys1Equity-sys1Equity[30]," ",sys1Equity);
print(d," ",sys2Equity-sys2Equity[30]," ",sys2Equity);
print(d," ",sys3Equity-sys3Equity[30]," ",sys2Equity);}
print(d," MultiAgent ",multiAgent);

//system parameters
vars: movAvgLen(200),numDownDays(3),numDaysInTrade(2),stopLoss(5000);
Switch ( multiAgent )
Begin
Case 1:
movAvgLen = 200;
numDownDays = 2;
numDaysInTrade = 15;
stopLoss = 7500;
Case 2:
movAvgLen = 140;
numDownDays = 3;
numDaysInTrade = 9;
stopLoss = 2500;
Case 3:
movAvgLen = 160;
numDownDays = 3;
numDaysInTrade = 15;
stopLoss = 2500;
End;
// Actual system execution
value1 = countIf(c < c[1],numDownDays);

if multiAgent <> multiAgent[1] then print(d," ---->multiagent trans ");

if c > average(c,movAvgLen) and value1 = numDownDays then
begin
if multiAgent = 1 then buy("Sys1") next bar at open;
if multiAgent = 2 then buy("Sys2") next bar at open;
if multiAgent = 3 then buy("Sys3") next bar at open;
end;
if barsSinceEntry >= numDaysInTrade then
sell next bar at open;
if marketPosition = 1 then sell next bar at entryPrice - stopLoss/bigPointValue stop;
Cool usage of a switch-case and agent determination

If this seems over your head…

Get one of my books are check out Jeff Swanson’s course.  EasyLanguage has so many little nuggets that can help you define your algorithm into an actionable strategy.  You will never know how your strategy will work until your program it (properly) and back test it.  And then potentially improve it with optimization.

Multi-Agent Results

 

 


Discover more from George Pruitt

Subscribe to get the latest posts sent to your email.

Leave a Reply