5
$\begingroup$

I have been puzzling over how to vectorize the following function:

tradeplNonVectorized[signals_, periodPL_] := 
 Module[{tradePL = {}, PL = 0},
  For[i = 2, i < Length[signals - 1], i++, 
   If[signals[[i]] == 0, 
    If[signals[[i - 1]] != 0, PL = PL + periodPL[[i + 1]];
     AppendTo[tradePL, PL];
     PL = 0;], 
    If[signals[[i - 1]] == signals[[i]] || signals[[i - 1]] == 0, 
     PL = PL + periodPL[[i + 1]];, AppendTo[tradePL, PL];
     PL = periodPL[[i + 1]];]]];
  tradePL]

Which works as follows:

signals = {0, 1, 0, -1, -1, -1, 1, 1, 0, 0};
periodPL = {0, 0, -0.0150, 3.0000, 0.9850, -0.0150, 1.0000, 
   1.0000, -3.0150, 0};
tradePL = tradeplNonVectorized[signals, periodPL]

{2.985,1.97,-2.015}

What the function is trying to do is group and total the periodic PLs into trades, which span periods of varying lengths.

A trade at period t is triggered by a signal at period t-1, and terminates when the sign of the signal changes. In the given example, there are three (pairs of) sign changes in signals, and therefore three trades. I want to group and add the periodPL values so that the total for each group is the PL for the corresponding trade.

I am actually trying to find a vectorized solution in Matlab and Python too, so bonus points for anyone who can provide those also!

$\endgroup$
2
  • $\begingroup$ Are there ever cases in the signal where you can have something like {0, 1 ,0, 1, ...}? That is, there are zeros in between ones so that the sign doesn't actually change? $\endgroup$
    – march
    Mar 31, 2023 at 20:11
  • $\begingroup$ In other words, can you be a little more specific about the structure of signals? If it's always one zero in between a 1 and a -1 or a -1 and a 1, then this will be pretty easy to do. Otherwise, some post-processing of signals first will be necessary (at least as I'm envisioning the problem). In addition, suppose that periodPL was non-zero in the first two entries. Would we include these as part of the first sum (that resulted in 2.985)? (After trying something, your code says the answer is no, but I want to make sure.) $\endgroup$
    – march
    Mar 31, 2023 at 20:20

2 Answers 2

6
$\begingroup$

Here's another way to do it, by separating each of the conditions for evaluating the values and using Sow/Reap and condition checking. Each proc performs one step of the process with a single value of the condition at time t and t-1. Then you can Fold through the list of values:

Clear[proc]
proc[pl_, {signal1_, signal0_, periodPL_}] := (Sow[pl + periodPL]; 0.) /;
  (signal1 == 0 && signal0 != 0)

proc[pl_, {signal1_, signal0_, periodPL_}] := (pl + periodPL) /;
  (signal1 != 0 && (signal1 == signal0 || signal0 == 0))

proc[pl_, {signal1_, signal0_, periodPL_}] := (Sow[pl]; periodPL) /;
  (signal1 != 0 && ! (signal1 == signal0 || signal0 == 0))

proc[pl_, {signal1_, signal0_, periodPL_}] := pl

trade[signals_List, periodPL_List] := With[
  {data = Transpose@{signals[[2 ;;]], signals[[;; -2]], Append[0]@periodPL[[3 ;;]]}},
  Last@Last@Reap@Fold[proc, 0, data]]

(*demo*)
signals = {0, 1, 0, -1, -1, -1, 1, 1, 0, 0};
periodPL = {0, 0, -0.0150, 3.0000, 0.9850, -0.0150, 1.0000, 1.0000, -3.0150, 0};
trade[signals, periodPL] (* {2.985, 1.97, -2.015} *)

Alternatively, you can do away with the condition checking by matching the patterns directly:

Clear[proc]

nonZeroQ[x_] := (x != 0)

proc[pl_, {0, signal0_?nonZeroQ, periodPL_}] := (Sow[pl + periodPL]; 0.) 
proc[pl_, {signal1_?nonZeroQ, signal1_, periodPL_}] := (pl + periodPL) 
proc[pl_, {signal1_?nonZeroQ, 0, periodPL_}] := (pl + periodPL) 
proc[pl_, {signal1_?nonZeroQ, signal0_, periodPL_}] := (Sow[pl]; periodPL) 
proc[pl_, {0, 0, _}] := pl 

(*demo*)
trade[signals, periodPL] (* {2.985, 1.97, -2.015} *)

Given that there are only nine conditions that proc has to satisfy, you might even just think about expanding them out as explicit patterns...that's left as an exercise for the reader...

$\endgroup$
1
  • $\begingroup$ That's great - thank you Joshua! $\endgroup$
    – MMAUser
    Apr 1, 2023 at 7:15
7
$\begingroup$

You can use SequencePosition to find the changes in sign and FoldPairList to get the runs from these starting positions.

With

signals = {0, 1, 0, -1, -1, -1, 1, 1, 0, 0};
periodPL = {0, 0, -0.0150, 3.0000, 0.9850, -0.0150, 1.0000, 1.0000, -3.0150, 0};

Then

tradepl[signals_, periodPL_] :=
 Module[{runs},
  runs = Sort[
    SequencePosition[signals, {a_, b_} /; a != b, Overlaps -> False]
   ];
  runs = FoldPairList[{y, a} |-> {{Last@y + 1, Last@a}, a}, First@runs, Rest@runs];
  Extract[periodPL, Thread[{Span @@@ runs}], Total]
  ]

giving

tradepl[signals, periodPL]
{2.985, 1.97, -2.015}

and

tradepl[{1, 0, 1, 0, 1, -1, 0, -1, 0, 1}, periodPL]
{2.985, 0.97, 2., -3.015}

Hope this helps.

$\endgroup$
1
  • 1
    $\begingroup$ Good grief. Five functions I have never seen/used before. It’s going to take ChatGPT 10 years to get to this kind of Ninja level. Bravo! $\endgroup$
    – MMAUser
    Mar 31, 2023 at 22:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.