Portfolio Boss Documentation

Cyber Code Genetic Programming

 

 

The Cyber Code is a new feature for the Divine Engine, as part of the Evolutionary mode. It allows the Divine Engine to create trading indicators from scratch, by writing computer code on its own.

When the rest of the world use well-known indicators such as Moving Average, Relative Strength Index, or Bollinger Bands, here you'll keep your market edge even sharper with completely unique trading indicators. With the help of Genetic Programming, these codes evolve throughout the generations, in tandem with the evolution of strategies we already discussed. So it's an even more dynamic evolutionary progression, in which not only the strategies are evolving, but the codes as well, the nuts and bolts of each trading indicator.

You're not constrained to the rigid set of mathematical formula and specific market data as found in built-in trading indicators. Instead, the Cyber Code engine constructs and de-constructs myriad formulas, mixed in with various market data, to create the ultimate technical indicators.

Note, you need a certain license to access the Cyber Code engine. Please contact our Customer Support to upgrade your license.

 

This page is divided into three sections:

 


 

Practical Use

 

The use of Cyber Code is generally similar to the standard evolutionary backtest we discussed previously. The main difference is that the new rules (indicators) added by the Divine Engine are exclusively Cyber Code rules. None of the built-in indicators will be slapped to the strategies it created.

 

1.  First, you must set the “Search Algorithm” property to “Evolutionary”.

 

 


 

2.  Then get to the property “Rule Set Parameterization”, and choose the option “Generate Cyber Code” there. This is the key setting.

 

 


 

3.  Notice the cogwheel icon  beside that property. This opens the Cyber Code Configuration window. It allows you to define how the codes are generated.

 

 

You may select any of the default profiles here (and then press “Close”), or leave the window alone. Each profile has its own way of generating the codes; and you can create your own profile by toggling ON the “Advanced Mode”. But for now, we won't concern ourselves with its detail. Should you be ready to tweak the Configuration, you can read about it here.

 


 

4.  Now the rest of the properties–on the Backtest Parameterization Panel–can be left to their defaults; or you can tweak them however you wish. Consult the help page for Backtest Parameterization Panel to understand each of those properties.

 

 

Don't forget to add the Fitness Functions according to your goals. We have a new metric called Total Trades/Cyber Code Expression Count which can be useful to reduce the occurrence of useless code (introns).

Next, add the Portfolio(s) of your strategy as usual. And make sure the System Settings properties are set to your liking–or you can leave them at default. Note, you can also parameterize any of the System Settings properties, as described here and here. Ditto the In Sample Periods (on the Backtest Panel) can be left at default, or set as described here.

 


 

5.  Assuming you're creating strategies from scratch, the Buy Filters, Ranking, and Sell Filters panel are left empty. This way, all rules and filters are exclusively of the Cyber Code type. You can start the Divine Engine backtest right away by clicking the button “Start the Boss”.

 

 

A confirmation dialog shows up, telling you the estimated compute credit consumption for this backtest. Read more about this dialog here.

 


 

6.  But if you decide to insert some built-in indicators, you can do so now before the Divine Engine backtest. Simply add the Buy, Rank, and/or Sell filters as usual.

 

 

Maybe you trust the RSI filter so much, and no trading strategy is complete without it, then add the RSI Filter. Or, you want to kickstart the evolutionary progression with the tried-and-true 200-day Simple Moving Average, then add the SMA Position Filter. Even if you add these built-in filters, the new rules added (and evolved) by the Divine Engine are solely of the Cyber Code type; the built-in ones will stay as you set them.

Now notice the leftmost parameter (flag) at those filters you added. These built-in rules can be set to Enabled, Disabled, or Random.

 

 

If you want a filter to stay no matter what, set it to Enabled. If you want the Divine Engine to decide whether a filter is useful or not, set it to Random. You can also parameterize their fields as you see fit, so the Divine Engine finds the best parameter values for them.

 


 

7.  Now start the Divine Engine backtest by clicking the  button as usual. Once finished, load any of the top strategies, one with good Fitness score (both IS and OOS), as the active strategy. Simply click the Load Parameters button from the Divine Engine Results Tab.

 

 


 

8.  Notice the rules added by the Divine Engine are labelled as “Cyber Code Generator Rule/Filter”, whereas those built-in rules (those you added yourself) have their respective names displayed.

 

 

The Cyber Code buy and sell filters can't be manipulated by you; its parameters and values are “baked in” to the code itself. As for the Cyber Code Ranking Rules, you can still manipulate their “Highest/Lowest” parameter, as well as the Offset and Weight parameters; their code, however, can't be edited too.

Now to see the code contained in each rule, click the Curly Bracket icon. It shows you the pseudo-code–i.e. the simple, human readable format of the code–that drives the technical indicator. We'll discuss later how to decipher the code's workflow.

 

 


 

9.  If you load a top strategy and decide to make a baseline from it (for further Divine Engine backtest), notice that the Cyber Code rules may have the flag “Replaceable/Not Replaceable” instead of the “Enabled/Disabled/Random” for the built-in rules.

 

 

A Cyber Code rule that's set to “Replaceable” may get replaced by a completely new rule, but the chance is quite low as otherwise the code in that rule doesn't have a chance to evolve. More likely, a “Replaceable” rule will stay, but some of its code have been evolved.

 

 

Note:

If you encounter a problem with running The Boss, you may want to check the Portfolio Boss Status Page and see if there are indeed problems with The Boss service. There you can also check the status of market-indicators download services, that is if you added TAP or Grayscale Cryptocurrency ETFs data in the Configuration window.

 

 


 

Mechanism

 

This section explains the mechanism (workflow) of the Cyber Code engine.

Each rule/filter is created by applying random blocks of code. These blocks can be found on the Cyber Code Configuration window (the Advanced Mode); look under the columns “Operation Type”.

 

 

In the beginning, a root block (root node) is created. Imagine an upside down tree: the root is the uppermost expression (operation) that produces the final result of the entire tree. The Cyber Code engine picks a random block for this root node, for example the Addition expression. This expression obviously has two operands (the two components that will be added); so from the root node, the tree branches in two. Thus the engine picks two more random blocks; for example Square Root and Division operations.

 

 

The Square Root has only one operand, so it does not branch; let's say it picks a constant or an input value to be square-rooted. When the engine picks a block like this, either a constant (for example a Pi 3.1416), or an input value (for example volume, closing, high, or low price), that is, a block that requires no operands, just a value, then this branch of the tree ends at this terminus. The Division branch though, requiring two operands, will branch further in two.

 

 

This process of branching and finding random blocks of code continues until the maximum “depth” of the tree is reached. In which case, the engine picks an input value as the terminus (the “leaves” of the tree). The flow of the code starts from the bottom (leaves) toward the root node at the top. Therefore constants and input values are always the building blocks of any Cyber Code rule; these are processed first and foremost. Note: The maximum depth of the tree can be set from the “Max Operation Chain Length”, in that Configuration window.

 

 

There are two types of trees: those for the Filters (Buy & Sell), and those for the Ranking Rules. The Filter trees yield a true or false value once they're fed the historic price data of a given instrument (the inputs and constants); so, true means it's a buy. Therefore the root node of such trees are always logical or relational operators; you can see such operators under the “Conditions” Operation Type on the Configuration window. As for the Ranking trees, they yield a number once they're fed the historic price data of an instrument. This number is then used to rank that instrument among other instruments.

 

All strategies in a population of 64, for example, are given these Cyber Code rules & filters. The standard evolution then applies: these strategies are backtested to find their metrics' values, and then tournaments are commenced to define the 32 survivors. After that, the usual cross-breeding: the two offspring strategies are given rules & filters picked randomly from the parents' rules & filters.

 

 

Now, comes the Cyber Code cross-breeding: a Cyber Code rule (from one Child) is randomly paired to another Cyber Code rule (from the sibling), and these two rules swap a node. If a rule doesn't find a pair, it'll be left alone as it is. One subtree (node) is exchanged for another subtree; it could even be the whole tree, or just a certain leaf (terminus). Which node gets picked is random, but the engine takes into account the “depth” of the two nodes, so they're of the same size, generally.

 

 

The property “Cross-Breeding Rate” defines the maximum depth of the node to be cross-bred. With a low rate, only the leaf/terminus is swapped, whereas a high rate may swap the entire tree. In summary, there are two levels of cross-breeding: first is the usual allocation of rules from the parents, and the second is the exchange of DNA codes between the rules. Note, it is also possible that two identical rules are crossbred; the diversity then comes from the different nodes each rule is swapping.

 

Once the offspring strategies are created, there's the usual mutation phase. But those Cyber Code rules are mutated differently: obviously, a Cyber Code rule doesn't have a customizable parameter. So its code (nodes) are the ones mutated. For every node in the tree, there's a percent chance it gets mutated. Of course, this chance is defined from the “Mutation Rate” property. When a node gets mutated, one of two things may happen:

  • The expression type is changed. That is, for example, an Addition expression becomes a Subtraction expression; or a Square Root becomes a Negation, etc. In essence, the amount of operands must not change. As you see, Addition and Subtraction both require two operands; whereas Square Root and Negation require but one operand. With this type of mutation, the original operand(s) is kept, be it a constant, input, function, or a further branch.
  • The node type is changed. Now, this mutation allows a more radical change to the node. So for example, a unary expression becomes a binary expression (from one operand to two operands), or even changed into a terminus (constant or input). The original operand(s) may be used in the new node, so the code logic further down the tree is not affected too much. If it requires more operands, then the engine may pick a random block as usual.

 

 

Other “randomizable things” are mutated too, such as replacing an entire rule if it's flagged “Replaceable”, or any parameterized fields on a built-in rule you added yourself. But note, a Cyber Code rule has only a slight chance of getting replaced entirely, to avoid truncating the code's evolution prematurely.

 

Now that the offspring strategies are created through standard & Cyber Code cross-breeding, and standard & Cyber Code mutations, the standard tournaments are conducted again. These strategy-level tournaments hit two birds with one stone: not only they select the fittest strategies, but also the fittest Cyber Code rules (the fittest trees), as these rules are part and parcel of a given strategy. So there's no such thing as Cyber Code tournaments in which the rules battle each other out.

 

 

This whole process then repeats for the next generations until the evolution is finished. Thus, as you can see, with the Cyber Code evolution, not only the standard evolution is applied, but the code level cross-breeding and mutations as well. This allows for a more dynamic evolutionary progression to create the ultimate strategies.

 

Let's now learn how to translate the pseudocode into a tree diagram. This way, you'll get the gist of how the indicators work. Note that the Cyber Code engine is continually being improved, so things explained here may become obsolete and irrelevant. In this example, we have a ranking rule; click on the “curly brackets” icon  to open the pseudocode:

 

 

To read this code, understand that the upper lines are usually the outer branches, containing the leaves. The root node is usually at the bottom. The computer executes the code from top to bottom, unless there's a “control flow” statement like “if” or a shared node. “var” indicates the declaration (creation) of a variable. “v0” is the name of that variable. Think of a variable as a container with a name. A variable may contain an expression, a mathematical operation, function, constant, input, or any code that needs to be contained. Note: Sometimes, a variable is declared without “var” preceding the name, but the data type associated with that variable, such as “double” or “bool”. “double” means the variable yields numerical value with decimal points, and “bool” means it's a variable that yields a true or false value (1 or 0).

In our case, the variable “v0” contains the function SUM, used for finding the sum total of the data sample. So let's draw a circle with a name “v0”, which contains the SUM function.

 

 

Now, a variable is usually a node that branches further. In this case, it is a node with two operands. The first operand is the “p.AdjustedClose”, that is, the adjusted closing price of an instrument. The second operand is the period of “121” days. These two operands branch out from the “v0” variable. As they are merely input & constant, they don't branch out more. Thus, they are the terminus (leaves). This “v0” subtree can be understood as “calculate the sum of all adjusted closing prices from the past 121 days”.

 

Now let's get to the second line. Here we have another function assigned to the variable “v0”.

 

 

It's common for a variable to have multiple assignments like this. That means, whenever the variable is called later down the line, it uses the latest assignment instead of the prior ones. The expression or function on the previous assignment has been calculated, as it is written earlier (above).

This next assignment of “v0” contains the function “Math.Max”, with two operands: “v0” of the previous assignment, and “p.AdjustedLow” signifying the current/today's adjusted low price of an instrument. Since the previous “v0” is the operand of the latest “v0”, we draw a line upward, connecting the previous circle into this new circle called “v0”. This circle contains the operation “Math.Max”.

 

 

Another branch of this circle, is the input value “p.AdjustedLow”. Thus, the new “v0” node tells the computer “find which operand has the greatest value: either the previous ‘v0′ (i.e. the sum of 121 days' worth of adjusted closing prices), or, the current adjusted low price of this instrument”. Let's say the “p.AdjustedLow” is the greatest, then this v0 node yields the value of “p.AdjustedLow”.

 

 

On the third line, we have a new variable declared, “v1”. It simply contains the subtraction operation between “p.Volume” (the amount of shares traded for a given day) and the Pi constant (3.1416). So let's draw a new circle called “v1” with the subtraction operation on it, with two operands: “p.Volume” on the left, and “π” on the right.

 

 

This subtree is still separated from the rest, as it doesn't contain any previously assigned variables (i.e. “v0”). It connects to the “v0” subtree on the fourth line.

 

 

Now, on this final line, usually we encounter “return”, indicating the root node, where the final value of the whole tree is calculated. “return” simply means this whole code/function/tree must “yield” or return a final value.

This root node contains the multiplication operation between “v0” and “v1”. Remember, the “v0” used here is the latest assigned, not the previous one. So we draw a line connecting the latest “v0” circle, to this root node, a circle that contains the operator “*” for multiplication. The “v1” circle is also connected toward this root node.

 

 

This root node is not a variable, so we don't put any label on the circle. The resulting number (product of multiplication) is then used to rank this instrument, in relation to the other instruments. Thus you get the general idea how the tree diagram is constructed.

 

Sometimes you may encounter logical or relational operators as shown in these code:

 

 

These are usually found on the Buy/Sell Filter trees, as they must yield a final true/false value (unlike the Ranking trees, which yield a number). In the first example, the node yields a true if the left operand is “greater than or equal” to the right operand; anything other than that yields a false.

 

 

In the second example, the node yields a true if both operands are true. As you see, the two operands are themselves nodes with relational operators, and if these nodes both yield true, the parent node yields a true; otherwise it's a false.

 

 

You may also encounter conditional statements like “if” or “?” :

 

 

In the first example, the expression after “if” (inside the parentheses) is used to define which path is taken: if the expression yields a true, then the statement after “if” (between the curly brackets) is executed, but if it's false, then the statement after “else” is executed. The second example is similar to “if”, but abbreviated. The expression before “?” defines which path is taken: if it yields a true, then the statement after “?” is executed; but if it's a false, then execute the statement after “:”. Usually, such a conditional statement is used to check/avoid division by zero.

When you draw the tree of a conditional statement you do it like this:

 

 

as if it's a node with three operands, albeit it's not an actual node signified by the dotted circle. Therefore the “if-then-else” circle and the conditional expression do not actually exist in the tree; they merely serve as a visual aid. The real node (subtree) comes from which path is actually taken (the latter two operands).

The same thing applies if there are nested conditional statements, like in this example here:

 

 

This is because the conditions merely define which path is taken. Each nested condition, taken individually, does not always alter the value itself; it may or it may not do so. So conditional statements, even if nested, do not increase the tree's depth.

 

There's also an “if” statement with “double.IsNaN( )” as shown in this pseudocode:

 

 

Such condition simply checks whether the variable inside the bracket yields a number. If it's a number, it returns false, and the “else” statement is executed (or if there's no “else” statement, the computer simply skips the “if” and continues to the next line). If it's not a number, it returns true, and the expression inside the “if” is executed.

In some cases, code bloat or “introns” are encountered. It's when the code don't make any sense (self-defeating, redundant, or inefficient). This is an inevitable side effect of the evolution's random nature. Our developers are continually finding ways to improve code efficiency and reduce such bloat.

 

Note:

 

  • The naming of the variables denotes the expressions they contain, for example “b” means a variable that uses a “boolean” expression (yielding true or false), either through logical or relational operators; “v” denotes a variable that contains mathematical functions, arithmetic expressions, constants, or inputs, thus returning a number; “se” is for a “shared expression” variable, containing mathematical expressions shared by other nodes; and “sc” signifies a “shared condition” variable, containing boolean expressions shared by other nodes.

 


 

Cyber Code Configuration Window

 

Now we will explore thoroughly the Cyber Code Configuration window.

This window is used for configuring the way the codes are generated. Open this window by pressing the cogwheel button beside the “Rule Set Parameterization” property. Make sure you set that property to the option “Generate Cyber Code”.

 

 

When you first opened this window, the “easy mode” is displayed:

 

 

This mode simply lists all the profiles that you can load, either the “Default Profiles” (built-in profiles), or the custom profiles you saved before (under “My Saved Profiles”). Each profile contains its own settings on how the code will be generated. To load a profile, simply click it, and press “Close”. Now to get to the advanced mode, toggle ON the “Advanced Mode” (at the top-right corner). This mode allows you to create your own profiles, by manipulating the myriad controls there. Let us now explore each panel in this Advanced Mode:

 

 

 


 

1.  Available Profiles  –  This panel lists all the profiles, similar to the “easy mode”.

 

 

Click on a profile to load it; and you'll see other panels have their settings changed. After you clicked a profile, press the “Close” button and it will be used to generate the code. Note, the active profile has its name displayed on top of this window:

 

 

Now, take note of the four buttons at the bottom-left of this window:

 

 

“New” lets you create a new profile. Click on that button, and a new profile is created under “My Saved Profile”. This profile (called “New Cyber Code Profile”) is automatically set with the default values. To rename this profile (or any profile under “My Saved Profile”), click its pencil icon and edit the name; once done, press Enter on your keyboard (or click the checkmark icon).

 

 

Since the new profile is created with the default settings, you may want to manipulate other panels as you see fit. Then, to save your customization, press the “Save” button. This “Save” button works on any profile you selected (under “My Saved Profile”); that is, any customization you made will be saved to that selected profile.

 

 

Note that profiles under “Default Profiles” can't be customized. You can copy them instead, so they're listed under “My Saved Profiles”. To do that, select a profile and press the button “Copy”. The duplicate has the suffix ” – Copy” on its name. So select that duplicate, manipulate its settings, and press the “Save” button. The same thing applies for profiles under “My Saved Profiles”.

 

 

Finally we have the “Delete” button, to remove a useless profile from “My Saved Profiles” (built-in profiles can't be deleted). Simply select a profile, click the “Delete” button, and press “Yes” on the confirmation dialog.

 

 

Notes:

  • If you manipulated the settings and then click on another profile without saving your customization first, a warning dialog is displayed.
  • It is possible to customize the settings and press the “Close” button, without saving first. When you do a Divine Engine backtest with such customization, it will be automatically saved to the profile (which you customized) and be used to generate the code.
  • Obviously, the profiles are available to any strategy you wish to use the Cyber Code on.
  • If you load a top strategy created through the Cyber Code evolution, you also load its particular Cyber Code configurations. The Configuration window is loaded with the settings used for that top strategy.

 


 

2.  Cyber Code Generator Settings  –  This panel contains the general settings for the Cyber Code engine.

 

 

Max Operation Chain Length: This defines the maximum amount of vertical element (depth) in the tree. The resulting trees are capped at this depth; but it may be less than this. You can see in this tree diagram, it has a depth of 5, with each element highlighted. That is to say, each element is either a node or a terminus:

 

 

This is highly useful to limit the vertical growth of the trees, thus reducing code bloat and introns. The minimum value you can set here is 3, while the maximum is 50. A smaller value yields simpler trees, whereas larger values create more complex trees with many operations and data considered. Keep in mind, a large value not only increases Divine Engine backtest duration, but it may stop the evolution altogether as it exceeds computational memory (a warning will be shown to reduce this value).

 

 

Max Lookback: This affects the “Moving Values” and “Indicators” operation types, such as the EMA, RSI, Lookback, Standard Deviation S, and Sum:

 

 

It defines the maximum amount of days that these operations can look back. As you already know, for example, an Exponential Moving Average requires a certain amount of days (a period) to calculate its result. Such period may be less than what you set here, but never more. The longer the period, the more memory (RAM) will be used, thus the longer the Divine Engine backtest will be.

 

 

Constant Range: This defines the range that random numbers are generated from. “Random Number” is part of the “Constants” type:

 

 

Such random numbers are used in the mathematical expressions. When you edit this property, the right field must be greater than the left field, otherwise there's a warning. For example, a range of -10 to 10 means that the random number generated could be -10, -6, 1, 9, or even precise decimal value such as -9.76898975. Greater range means more variance in the random numbers used.

 

 

Shared Expression Reuse Pressure: This defines the likelihood that a “shared expression” is reused. A “shared expression” is a node with the variable name “se” (like “se0” and “se1”) that contains a mathematical expression. This node is “shared”, that is, it can be used by two or more nodes, by referencing it. That way, it serves to control the flow of the code; you can think of it as a node with multiple parent nodes.

 

 

Shared Condition Reuse Pressure: This defines how likely a “shared condition” is reused by other nodes. “Shared condition” is similar to “shared expression”, except it uses the variable “sc” (such as: “sc0” and “sc1”), and contains logical or relational expressions (that yields true or false) instead of mathematical expressions.

 

 

Rule Replacement Rate: This defines the percentage chance that a Cyber Code rule (the entire tree) gets replaced by a completely new tree during the mutation phase. The default value is 10%, and it's recommended to use such a low value. As stated earlier, the chance a Cyber Code rule gets replaced should be low, otherwise the code inside won't evolve properly.

 


 

3.  Operation Weights  –  This panel contains the various code blocks (a.k.a. “operations”), that will be chained together to form the complex trees. Here you can define the likelihood that each block will be used in the trees.

 

 

Essentially, there are two columns here: the “Operation Type” (shown in red) contains the name of the blocks, whereas “Weight” (shown in green) defines the probability they will be used. Remember that each “Weight” stands in relation to the other “Weights”; so a value of 1 and 1 are considered similar to 100 and 100. But note, a value of 0 means the block will never be used.

The smallest weight you can input is 0, while the maximum weight is 99999.9.

 

Now, there are at least 9 types of block here:

 

 

Inputs: These are the historic data of a given instrument (from your portfolio); the “raw” data that will be fed to the tree. They denote the “current” data, that is, whatever date the backtest is currently processing. These blocks form the leaves or terminus of any tree.

  • Adjusted Open: The adjusted opening price of the instrument, represented by “p.AdjustedOpen” in the pseudocode.
  • Adjusted High: The adjusted high price of the instrument, represented by “p.AdjustedHigh” in the pseudocode.
  • Adjusted Low: The adjusted low price of the instrument, represented by “p.AdjustedLow” in the pseudocode.
  • Adjusted Close: The adjusted closing price of the instrument, represented by “p.AdjustedClose” in the pseudocode.
  • Open: The historical opening price of the instrument, represented by “p.Open” in the pseudocode.
  • High: The historical high price of the day for the instrument, represented by “p.High” in the pseudocode.
  • Low: The historical low price of the day for the instrument, represented by “p.Low” in the pseudocode.
  • Close: The historical closing price of the instrument, represented by “p.Close” in the pseudocode.
  • Volume: The number of shares traded for the instrument, for that given day; represented by “p.Volume” in the pseudocode.
  • Day of The Week: The “current” day of the week, converted into a number. As you well know, there are 5 trading days in a week. And “current” means whatever date the backtest is currently processing. For example, the backtest is currently being fed the candle from September 9th, 2014, which happened to be a Tuesday, therefore the second trading-day of the week. In a numerical format, we have the value “2”. This is represented by the code “p.DayOfWeek” in the pseudocode.
  • Day of The Month: The “current” day of the month. Similar to the previous one, but for one month, that is, there are 31 different values for this (non-trading days are also counted). It's represented by the pseudocode “p.DayOfMonth”.
  • Day of The Year: The “current” day of the year. For example January 4th, 2021 is the fourth (4) day of the year (holidays counted); so there are 365 different values for this. It's represented by the pseudocode “p.DayOfYear”.

 

 

Constants: Constants are simply fixed numbers (or a random number) used to modify a mathematical expression (for example, as a multiplier). Constants, just like the previously mentioned “inputs”, make up the leaves (terminus) of a tree.

  • Random Number: In the pseudocode, this block manifests as a random number, which falls anywhere between the lower and upper bounds as set on the property “Constant Range” discussed earlier.
  • Pi: The ratio of a circle's circumference to its diameter, which is the approximate number 3.1416. The pseudocode “π” represents the number.
  • Euler Constant: This is the base of the natural logarithm, which is the approximate number 2.7183. It's represented by the pseudocode ““.
  • Golden Ratio: The divine proportion, found in many natural phenomena, represented by the approximate number 1.618. It appears with the pseudocode “φ“.

 

 

Expressions: Standard Math: These blocks are the standard arithmetic operators. And as operators, they require operands; so they are nodes in the tree that branch further.

  • Addition: Adds two operands to yield their sum. It's represented by the operator “+” between the two operands.
  • Subtraction: Subtracts the right operand from the left operand, to yield their difference. It's denoted by the operator “-” in the pseudocode.
  • Multiplication: Multiplies two operands to yield their product, represented by the operator “*”.
  • Division: Divides the left operand by the right operand, to yield the size of each partition. Denoted by the operator “/” between the two operands.
  • Square Root: Finds the square root of an operand, represented by the pseudocode “Math.Sqrt( )”. It's a unary operator, where the one and only operand is put inside the brackets. In the tree diagram, simply draw it like the usual node, but with one operand.
  • Exponentiation: Repeated multiplication of the left operand by the amount of the right operand, denoted by the pseudocode “Math.Pow( , )” where the operands are put inside the brackets and separated by the comma.
  • Root Extraction: Finds the root of a number, by a certain depth (degree). The root of 25 with a depth of 2, for example, is called a “Square Root” of 25. In the pseudocode, root extraction uses the same function as exponentiation, i.e. “Math.Pow( , )”. The left operand will have its root extracted, while the second operand denotes the depth of the root. But note, the second operand is placed as the denominator of a fraction of 1, signifying the inverse of power.
  • Logarithm: Calculates the depth by which a number and its root are related. This is represented by the pseudocode “Math.Log( , )” where the left operand is the number whose root is on the right operand.
  • Modulus: Calculates the remainder of an imperfect division. For example, 7 divided by 3 is an imperfect division that yields 2, with 1 as the leftover crumb. It is represented by the operator “%” between the two operands. For example, 7 % 2 equals 1.
  • Min: Finds which of the two operands has the lowest value. Represented by “Math.Min( , )” in the pseudocode.
  • Max: Finds which of the two operands has the greatest value. Represented by “Math.Max( , )” in the pseudocode.
  • Absolute: Another unary operator represented by the pseudocode “Math.Abs( )”. It yields the absolute value of an operand; in other words, it always yields the positive value of that operand, regardless if it's actually negative.
  • Negation: The opposite of Absolute, i.e. it yields the negative value of an operand. But unlike Absolute, Negation is always the opposite of that operand's original value: if it's positive, then it becomes negative; but if it's negative, then it becomes positive (“Absolute” yields a positive value if the operand is already positive). Negation is represented by the “-” (minus sign) attached in front of a single operand.

 

 

Expressions: Trigonometry: These are trigonometric functions, and they serve as nodes with their own operands.

  • Sine: Calculates the sine of an operand. The operand serves as an angle (in radians). Its pseudocode is “Math.Sin( )”.
  • Cosine: Calculates the cosine from an operand (taken as radians), represented by “Math.Cos( )”.
  • Tangent: Calculates the tangent from an operand (taken as radians), represented by “Math.Tan( )”.
  • Arcsine: Calculates the angle (in radians) from a sine value. The operand is taken as the sine value, whose angle will be calculated. It's represented by “Math.Asin( )”.
  • Arccosine: Calculates the angle (in radians) from a cosine value (which is an operand). It's represented by “Math.Acos( )”.
  • Arctangent: Calculates the angle (in radians) from a tangent value (which is an operand). It's represented by “Math.Atan( )”.
  • Arctangent2: Calculates the angle (in radians) from a tangent value. This tangent value comes from dividing the left operand by the right operand. It's represented by “Math.Atan2( , )”.
  • Hyperbolic Sine: Calculates the hyperbolic sine of an operand (taken as radians). It's represented by “Math.Sinh( )”.
  • Hyperbolic Cosine: Calculates the hyperbolic cosine of an operand (taken as radians), represented by “Math.Cosh( )”.
  • Hyperbolic Tangent: Calculates the hyperbolic tangent of an operand (taken as radians), represented by “Math.Tanh( )”.

 

 

Expressions: Moving Values: These are statistical functions, with a period of days as their sample range. As the name suggests, the period is a moving window. That is, the entire range is shifted one day forward if the previous day has been processed.

  • Max: Finds the greatest value out of the sample. It's represented by the pseudocode “MAX( , )” where the left operand defines the type of data to be calculated (e.g. the closing price), and the right operand defines the period of days (e.g. 200 days). So for example, out of the last 200 closing prices, this function finds the highest closing price.
  • Min: Finds the smallest value of the sample. It's represented by the pseudocode “MIN( , )” where the left operand defines the data type, and the right operand the period of days; similar as in Max.
  • Variance S: Calculates the Variance of the sample (how varied the sample is). It's represented by the pseudocode “VARIANCES( , )” where the left operand defines the data type, and the right operand the period of days.
  • Standard Deviation S: Calculates the Standard Deviation of the sample (how spread out its values are from the average). It's represented by the pseudocode function “STDDEVS( , )” where the left operand defines the data type, and the right operand the period of days.
  • Sum: Calculates the total sum of the sample; represented by “SUM( , )” where the left operand defines the data type, and the right operand the period of days.
  • Lookback: Picks the value reported in the beginning of the period (for example, the closing price 200 days ago). It's represented by the pseudocode function “LOOKBACK( , )” where the left operand defines the data type, and the right operand the period of days.

 

 

Indicators: These are technical trading indicators encapsulated as functions. These indicators are widely used as the foundation for other, more complex, modern indicators.

  • Average Percent Range (APR): A measure of instrument's volatility in a period of days (shown in percent of the price). It's represented by the pseudocode “APR( , , )” that requires three operands. The left and middle operands are usually the adjusted high and low prices of the instrument, as they are the main ingredients of APR calculation. The right operand is, as usual, the period of days.
  • Average True Range (ATR): This is another yardstick of instrument's volatility similar to APR, but given as a dollar value instead of percent. It's represented by the pseudocode “ATR( , , , )” that requires four operands. The 1st and 2nd operands are usually the adjusted high and low prices (as in APR), the 3rd is the adjusted closing price, and the 4th the period of days.
  • Chande Momentum Oscillator (CMO): A measure of instrument's overbought and oversold conditions (above 50 and below -50, respectively), as well as trend strength. It is represented by the pseudocode “CMO( , )” where the left operand is usually the adjusted closing price (the only historic data required for CMO), and the right operand is the period of days.
  • Exponential Moving Average (EMA): This calculates the average of the data (e.g. closing price) from the specified period. That is, to smooth them out, with more emphasis on recent data instead of the distant past. EMA is commonly used as a support/resistance line. It's represented by the pseudocode “EMA( , , )” with three operands: the left is usually the adjusted closing price, the middle is the period of days, and the right operand is the smoothing constant (a larger value means more weight for the recent prices).
  • Kaufman Efficiency Ratio (KER): A gauge of instrument's trend strength (0 is a trading range, 0.8 is a strong up or down trend, for example). It's represented by the pseudocode “KER( , )” where the left operand usually takes the adjusted closing price, and the right operand the period of days.
  • Rate of Change (ROC): A measure of an instrument's momentum (overbought & oversold state) and velocity (trend strength). The ROC is stated in percent of the price at the period's start. It's represented by “ROC( , )” where the left operand is usually the adjusted closing price, and the right operand the period of days.
  • Relative Strength Index (RSI): This is another measure of overbought (beyond 70) and oversold (below 30) conditions. It's represented by the pseudocode “RSI( , )” where the left operand is usually the adjusted closing price, and the right operand the period of days.
  • Simple Moving Average (SMA): Similar to EMA, but is the simple averaging of the data, where all the data are weighted equally (old and new). It usually serves as a support/resistance line. And is represented by the pseudocode “SMA( , )” where the left operand is usually the adjusted closing price, and the right operand the period of days.
  • Correlation Coefficient (COR): A measure of relationship (correlation) between two data types, whether they're moving in tandem perfectly (a 1.0 correlation); moving perfectly contrarian to each other (a -1.0 correlation); or anything in between (0.0 indicates no correlation). This is represented by the pseudocode function “COR( , , )” with three operands: the left and the middle operands are the two data types being compared (e.g. the Closing Price of a stock, and, the Closing Price of the SPX index), while the right operand is the period of days.

 

 

Conditions: These are logical and relational operators (and some functions that check a value). Each of them is essentially a condition, that will yield a true or false value (1 or 0), depending on whether the condition is met. They may be used as a conditional expression in “if” statements, or as individual expressions in their own right.

  • Is Greater Than: The “>” relational operator, that will yield a true if the left operand is greater than the right operand; otherwise a false.
  • Is Greater Than or Equal To: The “>=” relational operator, that will yield a true if the left operand is greater than, or equal to, the right operand; otherwise a false.
  • Is Less Than: The “<” relational operator, that will yield a true if the left operand is less than the right operand; otherwise a false.
  • Is Less Than or Equal To: The “<=” relational operator, that will yield a true if the left operand is less than, or equal to, the right operand; otherwise a false.
  • Is Equal To: The “==” relational operator, that will yield a true if the left and right operands are equal in value; otherwise a false. Keep in mind, even if you disable this operator (its weight set to 0), it may still be used in conditional statements that prevent division by 0 (the one that uses ? and : as explained earlier). Such statements are important to prevent program error.
  • Is Not Equal To: The “!=” relational operator, that will yield a true if the left and right operands are not equal in value; if they're equal, it yields a false.
  • Is Not a Number: The “double.IsNaN( )” function, with the single operand inside the brackets. It yields true if the operand is “not a number” (NaN), and false if it's a number. NaN denotes “no value”. For example if an indicator function (such as RSI or EMA) does not yet have the minimum period of days required (at the beginning of the backtest date, for example), then the function returns NaN. Therefore “double.isNaN( )” is commonly used as a condition in “if” statements, to check whether an indicator function is ready to be used (already returns a value); if it's not, then use another value (e.g. closing price) until the minimum required period is met.
  • Is Positive Infinity: The “double.IsPositiveInfinity( )” function, with the single operand inside the brackets. It yields true if the operand is a positive infinity, otherwise a false. Positive infinity is a value that is infinite in the positive direction, exceeding the data type's memory allocation; for example, dividing a positive value by 0.
  • Is Negative Infinity: The “double.IsNegativeInfinity( )” function, with the single operand inside the brackets. It yields true if the operand is a negative infinity, otherwise it yields a false. Negative infinity is a value that is infinite in the negative direction, exceeding the data type's memory allocation; for example, dividing a negative value by 0.
  • AND: The “AND” logical operator, that will yield a true if both the left and right operands are themselves true; otherwise it yields a false. Thus each operand must also be a Condition type, be it logical or relational operator, or the three functions described before.
  • OR: The “OR” logical operator, that will yield a true if any operand (left, or right, or both) is true; it only yields false if both operands are false. So each operand must be a Condition type too, that returns either true or false.
  • XOR: The “XOR” logical operator, that will return true if both operands are unequal; if they're equal, it will return false. Equal means the operands are both “true”, or both “false”. So if they're unequal, e.g. “true” and “false”, this operator yields a true.

 

 

Expression Control Flow: These blocks control the flow of expressions (arithmetic operations, mathematical functions, etc) in the tree.

  • Conditional Expression: This is an “if” statement that controls the flow of expressions. In other words, the actual paths taken (if true, and if false) are mathematical expressions (with variables like “v” or “se”).
  • Shared Expression: This is a node that contains mathematical expressions, which can be shared (referenced) by two or more nodes in the tree. Because it is shared, it controls the flow of the program. It's contained within a variable called “se”.

 

 

Condition Control Flow: These blocks control the flow of conditions in the tree (conditions as represented by logical and relational operators, or the boolean functions).

  • Conditional Condition: This is an “if” statement that controls the flow of conditions. In other words, the actual paths taken (the nodes for if true, and if false) are conditional expressions (with variables like “b” or “sc”). But these nodes may contain sub-nodes that are mathematical expressions as well.
  • Shared Condition: This is a node that contains conditional expressions, which can be shared (referenced) by two or more nodes in the tree. Because it is shared, it controls the flow of the program. It's contained within a variable called “sc”.

 


 

4.  Additional Data Sources  –  This panel allows you to add exotic market data, as additional input to be considered by the Cyber Code engine (that is, in addition to the usual OHLCV data of your Portfolio).

 

 

Such exotic data are usually compiled by external sources. They serve like the usual inputs or constants in the pseudocode; each being a value (a percentage, for example) that can be used in any expressions.

To add them, click the button “Add Data Source”. A dialog appears, allowing you to select the additional data. Once you ticked them, click “OK” and they're listed on this panel.

 

 

You can set their weights as usual (from 0 to 99,999.9), or delete them with their trash-bin icon. The date column (First Date), shows you the first time the data source (ETF) was traded on the exchanges; and this affects the strategy's backtest range (earliest date possible).

Currently, we have three types of additional data (some of them may require a certain PB license):

 

  • TAP Premium

These are listed with the suffix “Premium” on the Add Data Source dialog:

TAP stands for True Asset Price, and it shows you the true value (price) that a fund/ETF should be trading at. It's calculated by dividing all its net assets (i.e. minus the liabilities) by the number of shares in circulation.

Then comes the Premium (or Discount) part: most of the time, these ETFs are traded at a price higher (or lower) than their True Asset Price. If they're trading lower than the TAP (i.e. discounted), their price will likely go up. If they're trading higher (at premium), their price will go back down to their True Asset Price.

This discount/premium is calculated by dividing the ETF's adjusted closing price by its TAP, and this is subtracted by 1. The resulting number is a factor of 1 (as percentage) that shows how much of a premium (positive) or a discount (negative) that the ETF is trading at compared to its TAP.

TAP Premium data are represented by the pseudocode “get(‘ETFname Premium')”, for example “get(‘ARKK Premium')”.

 

  • Grayscale Holdings/Share:

These have the “Holdings/Share” suffix on the Add Data Source dialog:

These are the amount of cryptocurrency held for each share of a Grayscale crypto ETF (e.g. its Bitcoin or Ethereum ETF). They show the US dollar equivalent of such cryptocurrency amount. And they are represented by the pseudocode “get(‘Bitcoin Trust Holdings/Share')” or “get(‘Ethereum Trust Holdings/Share')”.

 

  • QQQ Timebomb:

There's only one item for it:

This comes from a licensed-strategy called QQQ Timebomb, indicating the percentage of normal Positions versus the Cash Equivalent Positions. A value of 0.00 means all Positions are in Cash Equivalent (indicating bear market in the Tech sector), while a value of 1.00 means all are normal Positions (normal market condition). It's represented by the pseudocode “get(‘QQQ Timebomb')”.

 

Note:

These hundreds of ETF represent various indexes, asset classes, sectors, industries, and geopolitical regions. They range from corn to gold, Germany to China, futures and bonds, foreign currencies, cryptocurrencies, major indices, complex derivative indices, and even merger & acquisition index! All this to give you a unique edge on your strategy.

For a more direct effect to the strategy, you may include these ETFs as part of the strategy's Portfolio, Cash Equivalent, or the filters' parameter. Let's say you added SPY Premium, QQQ Premium, SH Premium, and PSQ Premium data sources:

 

 

You can then include SPY and QQQ on the strategy's Portfolio, while its Cash Equivalent is parameterized between SH or PSQ:

 

 

In other words, you may want to include the data sources related to your strategy's Portfolio. Use your creativity!

Now, if there are problems with downloading any of these data sources, you can check it from the Portfolio Boss Status Page.

 


 

5.  Additional Instruments  –  This panel allows you to add more market data to the Cyber Code engine, in addition to the usual OHLCV of the instruments (from your portfolio). But instead of exotic market indicators, you can add any instruments here (usually market indexes).

 

 

They're then taken as the usual inputs (the OHLCV data), with the ticker symbol preceding the input name. For example, instead of “p.AdjustedClose” it now says “SPX.AdjustedClose”.

To add an instrument, type its name or ticker symbol at the top-right field. From the dropdown that appears, select an instrument that matches your search. Or if you typed the ticker symbol correctly, simply press Enter on your keyboard and the instrument is listed on this panel. You can then set its weight as usual, or delete it with the trash-bin icon.

 

 

The “First Date” column simply shows the earliest date for the instrument's historical price data (which affects the backtest's earliest date).

Now, delisted instruments aren't allowed here (those with square brackets suffix enclosing a number). If you try to input a delisted instrument, a warning dialog shows up:

 

 


 

Note:

 

Visual indicators are also available for Cyber Code rules. As usual, you can see them under the Instrument Tab. Make sure you click on an instrument first (from the Positions Tab) so the Instrument Tab is populated.

 

 

Here, the Cyber Code visual indicators appear on the 2nd section (the middle chart).

On the “Indicators” dropdown, you'll see which line (visual indicator) belongs to what rule. For example, “Ranking Rule #2” means the Cyber Code Ranking Rule located second from top at the Ranking Panel; “Buy Rule #1” means the topmost Buy Filter at the Buy Filters Panel, and so on.

 

 

This dropdown lists the Ranking Rules first, then below that the Buy Filters, and at the bottom cluster there are the Sell Filters.

Each Filter may contain more than one visual indicator, depending on how that filter works. To understand how it works, open its pseudocode viewer, and look at the variable referred to in the dropdown list. For example, “Sell Rule #1: Cyber Code (v1)” refers to the variable “v1” in the pseudocode. The values yielded by “v1” (the latest assigned “v1”) is plotted by this visual indicator.

 

 

Always remember that Buy and Sell Filters, no matter how complex the code is, ultimately yield a true or false value (buy or not buy, sell or not sell). So their root node must contain a relational or logical operator between the variables represented in the chart (if “v1” is higher than “v2”, for example, then it's a buy).

When it comes to Ranking Rules, they have only one visual indicator each. This indicator shows the final value (number) yielded by the entire tree, used for ranking the instrument, in relation to the other instruments. Keep in mind that higher value for the indicator does not necessarily mean it's ranked higher; take a look at the rule's “Highest/Lowest” parameter as well.

Note, if an indicator seems so flat throughout its history, either it's indeed flat (the value doesn't change), or because the scaling of the chart has been taken over by another indicator with huge values. If it's the latter case, simply disable that other indicator (from the dropdown).

 

 

 

Back to Top