thinkscript if function useless in important case - thinkscript

The thinkscript if function fails to branch as expected in an important case. The following test case can be used to reproduce this severe bug / defect.
In a nutshell, an if statement may normally be used to prevent a function call from being executed if one of its function parameters is invalid. We show that this is not the case. In fact, both branches are executed, including the branch not meeting the if condition.
This absolutely defeats the purpose of the test of the if condition, the test that every if statement in every language has.
Following is some sample code that shows the problem on a chart. The result can be seen by clicking on the "i" message icon blinking in the left top corner of the chart:
Folding: 'from' cannot be greater than 'to': 1 > -1.
# Get the current offset from the right edge from BarNumber()
# BarNumber(): The current bar number. On a chart, we can see that the number increases
# from left 1 to number of bars e.g. 140 at the right edge.
def barNumber = BarNumber();
def barCount = HighestAll(barNumber);
# rightOffset: 0 at the right edge, i.e. at the rightmost bar,
# increasing from right to left.
def rightOffset = barCount - barNumber;
# This script gets the minimum value from data in the offset range between startIndex
# and endIndex. It serves as a functional but not direct replacement for the
# GetMinValueOffset function where a dynamic range is required. Expect it to be slow.
script getMinValueBetween {
input data = low;
input startIndex = 0;
input endIndex = 0;
plot minValue = fold index = startIndex to endIndex with minRunning = Double.POSITIVE_INFINITY do Min(GetValue(data, index), minRunning);
}
# Call this only once at the last bar.
script buildConditions {
input startIndex = 1;
input endIndex = -1;
# Since endIndex < startIndex, getMinValueBetween() should never
# be executed. However it is executed nevertheless.
plot minValue = if (endIndex > startIndex) then getMinValueBetween(low, startIndex, endIndex) else close[startIndex];
}
plot scan;
if (rightOffset == 0) {
scan = buildConditions();
} else {
scan = 0;
}
declare lower;

The question has the answer in its first sentence.
One might contemplate using the if statement (vs the if function). However, that is broken as demonstrated in
thinkscript if statement failure

At least as of April 2021, the documentation for the if reserved word says:
... while the if-expression always calculates both then and else branches, the if-statement only calculates the branch defined by whether the condition is true or false.
(bolding and italics mine)
Definitely confusing and unexpected behavior!

Related

-nan Error when trying to return the index of a class object

So in my intro to CS class, we're learning about classes and I'm having a lot of trouble right now.
This is project 2 part 2:
https://github.com/CSCI1300-StartingComputing/CSCI1300-Spring2022/blob/main/project/project2/project2pt2.md#question0
project 2 part 1 (part 2 builds on a lot of this stuff:https://github.com/CSCI1300-StartingComputing/CSCI1300-Spring2022/blob/main/project/project2/project2pt1.md#question4
I am specifically on number 6 for part 2.
I believe the problem lies in the below snippet of code:
while (k < numbMovies){temp = movies[k].getTitle(); //store movie title into temp arr
//convert temp to lowercase for case insensitivity
while (b < temp.length()){temp[b] = tolower(temp[b]);
b++;
}
//if the title we're searching is found in the movies array
if (temp == title)
{
movieIdx = k; //the kth movie will be our match, so save k as our index
numMatches++; //increment number of matches. if the title is found, this should be one at most
}
k++;
}
There is an issue somewhere with the line temp = movies[k].getTitle();, I think.
What this line is supposed to do is reference an array from the Movie class developed in part 1, store a title found at 'k' from the movies array into a string so that I can compare it to the string being passed into the function (this comparison is done in the line containing if(temp == title).
I then wish to save the value of k when temp == title as movieIdx, so that this can be used further in this function.
I think that the problem is with my referencing of the movies[] array, but I'm not sure what it might be.
Since there are a lot of things going on in this project, I'd be happy to send my files over to anyone who'd be willing to take a look.
Thanks in advance.
FURTHER CONTEXT:
I was attempting to test edge cases by returning 'movieIdx' immediately after setting movieIdx = k, just to see what was going wrong. Example of what I mean:
{
movieIdx = k; //the kth movie will be our match, so save k as our index
numMatches++; //increment number of matches. if the title is found, this should be one at most
return movieIdx;
}
-nan was returned consistently, although if I returned k outside of the first while loop (that contains the other while loops), I'd get the value that was expected (50, in this case, which equals numbMovies). If I returned movieIdx outside of the loop, I got 0.
This told me that there was some issue with the condition if(temp == title). I then did a test where I replaced 'temp' with a hardcoded value that I knew would be passed, "the prestige".
Doing this returned the expected value. I then knew that there must be some issue with 'temp' itself, and I have assumed that the issue therein would probably have to do with how I'm referencing the movies[] array.

Can't understand how it works (ThinkScript code)

I'm currently converting a ThinkScript indicator to python, however, I've run into this piece of code and I'm kinda confused on how it works:
input rollingPeriodMinutes = 60;
def factor = (SecondsFromTime(Market_Open_Time) / (60 * rollingPeriodMinutes) / 100);
def rolloverTime = if factor == Round(factor) then 1 else 0;
rec H1 = compoundValue(1, if !rolloverTime then if high > H1[1] then high else H1[1] else high, high);
rec H = compoundValue(1, if rolloverTime then H1[1] else H[1], high);
I can't really understand what is stored at the end in the variable "H". Can you help me understand?
Any help is really appraciated!! Thanks
input rollingPeriodMinutes = 60;
declares (defines) and sets a variable, rollingPeriodMinutes, to a default value of 60. The input declaration indicates that the user will be able to alter this value in the thinkorswim settings for this script.
def factor = (SecondsFromTime(Market_Open_Time) / (60 * rollingPeriodMinutes) / 100);
declares and sets a variable, factor to a calculated value. This uses the rollingPeriodMinutes value, above, as well as the SecondsFromTime function and a Market_Open_Time variable that must have been set elsewhere in the script.
def rolloverTime = if factor == Round(factor) then 1 else 0;
declares and sets a variable, rolloverTime to a boolean based on the if statement. This uses the factor variable above (1 is true and 0 is false in thinkscript).
rec H1 = compoundValue(1, if !rolloverTime then if high > H1[1] then high else H1[1] else high, high);
rec H = compoundValue(1, if rolloverTime then H1[1] else H[1], high);
rec is actually the same as def and has been obsoleted. Previously, it specifically declared a recursive variable; now one would just use def regardless. See the notes below for more information.
CompoundValue is an easy statement in thinkscript, but complicated to understand from the Learning Center reference.
In short, the declarations for H and H1 are saying 'going back 1 bar: if no data is present, then use the if statement to determine a value; else if data is present, then use the high value.
Broken out, the algorithm for H1 (where high is a reserved word for the high price for a given bar) could look like:
let numBarsBack = 1
if (data is present for the bar at numBarsBack) then
if (!rolloverTime == true) then
if high > (H1 value one bar previous) then H1 = high
else H1 = (H1 value one bar previous)
else H1 = high // thinkscript sometimes requires a "default" in `if` statements, even if there's no 3rd possible value
else (if rolloverTime == true) then H1 = high
else (if data is not present for the bar at numBarsBack) then H1 = high
*** See my complete description of how CompoundValue works in thinkscript at the SO question "Understanding & Converting ThinkScripts CompoundValue Function".***
Notes:
SecondsFromTime, according to the current thinkscript Learning Center reference looks like:
SecondsFromTime ( int fromTime);
Description
Returns the number of seconds from the specified time (24-hour clock notation) in the EST timezone. Note that this function always returns zero when chart's aggregation period is greater than or equal to 1 day.
Input parameters
Parameter Default value Description
fromTime - Defines time from which seconds are counted, in the HHMM format, 24-hour clock notation.
The Learning Center reference for rec says this:
rec
Notice: this is an article about an obsolete thinkScript® notation. Although rec variables are still supported by thinkScript®, they can be completely replaced by def.
Syntax
rec
Description
Enables you to reference a historical value of a variable that you are calculating in the study or strategy itself. Rec is short for "recursion".
Example
rec C = C[1] + volume;
plot CumulativeVolume = C;
This example plots the cumulative volume starting from the beginning of the time period.
and, finally:
Remember that thinkscript code is executed for every bar in a selected period. Ie, if you're looking at 10 days with a daily period, there will be a bar for each of the 10 days; and the script will run a loop, repeating the code for each of those 10 bars. As a result, the variables will have appropriate values for each bar.
Although the OP is wanting to convert a script to Python, if someone comes here interested in how thinkscript works, there are tricks to keep a value constant for an entire script (though this section of code does not include examples for that). For information on how to do this in thinkscript, see my answer to SO question "thinkscript - How to create a variable that retains its value".

Understanding & Converting ThinkScripts CompoundValue Function

I'm currently converting a ThinkScript indicator to C#, however, I've run into this CompoundValue function and I'm unsure how to covert it.
The documents reads :
Calculates a compound value according to following rule: if a bar
number is greater than length then the visible data value is returned,
otherwise the historical data value is returned. This function is used
to initialize studies with recursion.
Example Use:
declare lower;
def x = CompoundValue(2, x[1] + x[2], 1);
plot FibonacciNumbers = x;
My interpretation:
Based on description and example. It appears we are passing a calculation in x[1] + x[2] and it performing this calculation on the current bar and the previous bar (based on first param of 2). I'm unsure what the parameter 1 is for.
My Question:
Please explain what this function is actually doing. If possible, please illustrate how this method works using pseudo-code.
For the TLDR; crowd, some simple code that hopefully explains what the CompoundValue() function is trying to do, and which might help in converting it's functionality:
# from: Chapter 12. Past/Future Offset and Prefetch
# https://tlc.thinkorswim.com/center/reference/thinkScript/tutorials/Advanced/Chapter-12---Past-Offset-and-Prefetch
# According to this tutorial, thinkScript uses the highest offset, overriding
# all lower offsets in the script - WOW
declare lower;
# recursive addition using x[1] is overridden by 11 in the plot for
# Average(close, 11) below; SO `x = x[1] + 1` becomes `x = x[11] + 1`
def x = x[1] + 1;
# using CompoundValue, though, we can force the use of the *desired* value
# arguments are:
# - length: the number of bars for this variable's offset (`1` here)
# - "visible data": value to use IF VALUES EXIST for a bar (a calculation here)
# - "historical data": value to use IF NO VALUE EXISTS for a bar (`1` here)
def y = CompoundValue(1, y[1] + 1, 1);
# *plotting* this Average statement will change ALL offsets to 11!
plot Average11 = Average(close, 11);
# `def`ing the offset DOES NOT change other offsets, so no issue here
# (if the `def` setup DID change the offsets, then `x[1]` would
# become `x[14]`, as 14 is higher than 11. However, `x[1]` doesn't change.
def Average14 = Average(close, 14);
plot myline = x;
plot myline2 = y;
# add some labels to tell us what thinkScript calculated
def numBars = HighestAll(BarNumber());
AddLabel(yes, "# Bars on Chart: " + numBars, Color.YELLOW);
AddLabel(yes, "x # bar 1: " + GetValue(x, numBars), Color.ORANGE);
AddLabel(yes, "x # bar " + numBars + ": " + x, Color.ORANGE);
AddLabel(yes, "y # bar 1: " + GetValue(y, numBars), Color.LIGHT_ORANGE);
AddLabel(yes, "y # bar " + numBars + ": " + y, Color.ORANGE);
Now, some, er, lots of details...
First, a quick note on "offset" values:
thinkScript, like other trading-related languages, uses an internal looping system. This is like a for loop, iterating through all the "periods" or "bars" on a chart (eg, 1 bar = 1 day on a daily chart; 1 bar = 1 minute on a 1 minute intraday chart, etc). Every line of code in thinkScript is run for each and every bar in the chart or length of time specified in the script.
As noted by the OP, x[1] represents an offset of one bar before the current bar the loop is processing. x[2] represents two bars before the current bar, and so on. Additionally, it's possible to offset into the future by using negative numbers: x[-1] means one bar ahead of the current bar, for example.
These offsets work similarly to the for loop in C#, except they're backwards: x[0] in C# would represent the current x value, as it would in thinkScript; however, moving forward in the loop, x[1] would be the next value, and x[-1] wouldn't exist because, well, there is no past value before 0. (In general, of course! One can definitely loop with negative numbers in C#. The point is that positive offset indices in thinkScript represent past bars, while negative offset indices in thinkScript represent future bars - not the case in C#.)
Also important here is the concept of "length": in thinkScript, length parameters represent the distance you want to go - like the offset, but a range instead of one specific bar. In my example code above, I used the statement plot Average11 = Average(close, 11); In this case, the 11 parameter represents plotting the close for a period of 11 bars, ie, offsets x[0] through x[10].
Now, to explain the CompoundValue() function's purpose:
The Chapter 12. Past/Future Offset and Prefetch thinkScript tutorial explains that thinkScript actually overrides smaller offset or length values with the highest value in a script. What that means is that if you have two items defined as follows:
def x = x[1] + 1;
plot Average11 = Average(close, 11);
thinkScript will actually override the x[1] offset with the higher length used in the Average statement - therefore causing x[1] to become x[11]!
Yike! That means that the specified offsets, except the highest offset, mean nothing to thinkScript! So, wait a minute - does one have to use all the same offsets for everything, then? No! This is where CompoundValue() comes in...
That same chapter explains that CompoundValue() allows one to specify an offset for a variable that won't be changed, even if a higher offset exists.
The CompoundValue() function, with parameter labels, looks like this:
CompoundValue(length, "visible data", "historical data")
As the OP noted, this isn't really particularly clear. Here's what the parameters represent:
length: the offset number of bars for this variable.
In our example, def x = x[1] + 1, there is a 1 bar offset, so our statement starts as CompoundValue(length=1, ...). If instead, it was a larger offset, say 14 bars, we'd put CompoundValue(length=14, ...)
"visible data": the value or calculation thinkScript should perform if DATA IS AVAILABLE for the current bar.
Again, in our example, we're using a calculation of x[1] + 1, so CompoundValue(length=1, "visible data"=(x[1] + 1), ...). (Parentheses around the equation aren't necessary, but may help with clarity.)
"historical data": the value to use if NO DATA IS AVAILABLE for the current bar.
In our example, if no data is available, we'll use a value of 1.
Now, in thinkScript, parameter labels aren't required if the arguments are in order and/or defaults are supplied. So, we could write this CompoundValue statement like this without the labels:
def y = CompoundValue(1, y[1] + 1, 1);
or like this with the labels:
def y = CompoundValue(length=1, "visible data"=(y[1] + 1), "historical data"=1);
(Note that parameter names containing spaces have to be surrounded by double quotes. Single-word parameter names don't need the quotes. Also, I've placed parens around the equation just for the sake of clarity; this is not required.)
In summary: CompoundValue(...) is needed to ensure a variable uses the actual desired offset/number of bars in a system (thinkScript) that otherwise overrides the specified offsets with a higher number if present.
If all the offsets in a script are the same, or if one is using a different programming system, then CompoundValue() can simply be broken down into its appropriate calculations or values, eg def x = x[1] + 1 or, alternatively, an if/else statement that fills in the values desired at whatever bars or conditions are needed.
Please let me provide two equivalent working versions of the code in thinkscript itself. We use this approach to prove equivalence by subtracting the equivalent outputs from each other - the result should be 0.
# The original Fibonacci code with a parameter "length" added.
# That parameter is the first parameter of the CompoundValue function.
declare lower;
def length = 2;
def x = CompoundValue(length, x[1] + x[2], 1);
# plot FibonacciNumbers = x;
# Equivalent code using the `if` statement:
def y;
if(BarNumber() > length){
# Visible data. This is within the guarded branch of the if statement.
# Historical data y[1] (1 bar back) and y[2] (2 bars back) is available
y = y[1] + y[2];
}else{
# Not enough historical data so we use the special case satisfying the
# original rule.
y = 1;
}
plot FibonacciNumbersDiff = y - x;
Thinkscript "recursion" is a somewhat inflated term. The function name CompoundValue is not very helpful so it may create confusion.
The version using the if statement is more useful in general because when walking through the time series of bars, we often need a program structure with multiple nested if statements - this cannot be done with the CompoundValue function. Please see my other articles which make use of this in the context of scanning.
In Java, using the same structure, it looks like this:
int size = 100;
int length = 2;
int[] values = new int[size];
for(int index = 1; index < size; index++){
if(index > length){
values[index] = values[index - 1] + values[index - 2];
}else{
values[index] = 1;
}
}
The fundamental difference is the for loop which is not present in the thinkscript code. thinkscript provides the loop in a kind of inversion of control where it executes user code multiple times, once for each bar.

Traveling a Grid for a Palindrome

I have been trying to solve this problem for quite a bit now and have not been able to come up with anything other than a naive solution. Basically, I am given a character grid of size N in which I have to find the number of distinct paths from the upper-left to the top-right when only traveling down and to the right that give a palindrome.
Here is an example of a grid:
ABCD
BXZX
CDXB
WCBA
There are 12 palindromes in this grid such as "ABXZXBA". My solution was to walk through all paths in the grid and check if that string was a palindrome by keeping a character stack for the first N characters and popping each character for the next N characters and checking if they were the same. This solution times out when N gets too big and I am not sure how to proceed. Any psuedocode or suggestions would be much appreciated.
Just a theory - I haven't tried to work out the code yet:
You could start at the top left and bottom right and keep the paths in sync. As it is a palindrome there would need to be the same letters in the path from the bottom as from the top.
Each time you look for the next step in a path, check to see that there is a matching letter in the reverse step.
The reverse step's available path can be further constrained in that it can't get to the left or above the forward step.
Stop when the paths meet, which is complicated by the fact that they might end up on the same grid location (odd number of rows), or they might just meet up (even number of rows).
The back tracking and stack keeping may be a bit more complicated, as you have to account for (possibly) several choices of reverse step, but it should cut down the number of possibilities. It might be better to think of it as each step in the forward and reverse paths gives you a new (smaller) grid to check for palindromes.
Something like this?
(At least it stops if the path is not forming a palindrome or is out of bounds/sync.)
JavaScript code:
function f(m){
var stack = [[0,0,m.length - 1,m.length - 1,""]],
count = 0;
while(stack.length > 0){
var next = stack.pop(),
y = next[0],
x = next[1],
yr = next[2]
xr = next[3];
if (y - yr > 0 || x - xr > 0){
continue;
} else if (m[y][x] != m[yr][xr]){
continue;
} else if (y == yr && x == xr){
count++;
} else {
stack.push([y + 1,x,yr - 1,xr]);
stack.push([y + 1,x,yr,xr - 1]);
stack.push([y,x + 1,yr - 1,xr]);
stack.push([y,x + 1,yr,xr - 1]);
}
}
return count;
}
Output:
var t = [["A","B","C","D"]
,["B","X","Z","X"]
,["C","D","X","B"]
,["W","C","B","A"]];
console.log(f(t));
12

Unit testing with generated inputs and expected outputs

I'm writing some simple unit tests for a method which acts like a database query: it returns a list of results that can be customised using a limit (max of n results) and a page (the p'th set of n results). I'd like to test a few cases, such as:
Return all the results
Return the first page of a limited result set
Return an aribtrary page of a limited result set
Return the last page of a limited result set which overruns the total number of results
Return a page beyond the total number of results
I need to mock the results (effectively names) that are returned, so I'm just generating them with a counter to make a list of "Name #1", "Name #2", etc. for as many as I need. Each test looks (roughly) like:
public void testGetMockCandidatesLimited() throws Exception {
int numResults = 4;
setupMock(numResults);
results = queryFunction(...);
// Check that the expected number of results was returned
assertEquals(numResults, results.size());
// Check that the results are correct and in order
for (int i = 0; i < numResults; i++) {
assertEquals(result.get(i).getName(), "Name #" + (i + 1));
}
}
My question is: is it okay to 'generate' the expected answers in this way? This is a trivial example, but the next step was to write a test to write a test to get the second page of two results each, and I had:
final int TEST_LIMIT = 2, TEST_PAGE = 1;
// Check that the expected number of results was returned
assertEquals(numResults, results.size());
// Check that the results are correct and in order
for (int i = TEST_LIMIT * TEST_PAGE; i < TEST_LIMIT * TEST_PAGE + TEST_LIMIT; i++) {
assertEquals(result.get(i).getName(), "Name #" + (i + 1));
}
Now it just so happens that this test is 'correct' in the sense that i will take on the values that I expect it to and ensure that the results are "Name #3" and "Name #4". However it also happens that my queryFunction calculates which results to return in the same way (limit * page + limit kind of thing).
This is concerning because if the test generates the expected answers in the same way that the unit under test does, it won't detect if there's a bug in that approach. However naming the input values with constants makes the tests much more readable than just have magic integers between 1 and 4 plugged in everywhere.
What is the best way to deal with this situation, for both readability and maintainability (e.g. changing values in the future, should you need to)?
In asking this question I think I've convinced myself that the solution is to define the expected values as constants up front as well, such as:
final int LIMIT = 2, PAGE = 1, MIN_RESULT = 3, MAX_RESULT = 4;
... and then use those values in the assertions. This retains all the readability of named key values, and discourages the temptation to generate the expected outputs. In this case I would still be using a loop to generate each expected name between Name #MIN and Name #MAX, but the bounds themselves would be explicitly static.
Is this as good as it gets, or are there better ways to deal with this repetitiveness?