【问题标题】:How to create a variable that retains its value如何创建一个保留其值的变量
【发布时间】:2019-06-09 21:45:34
【问题描述】:

在 thinkscript 图表和扫描中,任何脚本都会针对每个条形执行多次。此类执行之间的程序状态存储在数组变量中,这些变量可以直接访问,也可以通过[]GetValue() 的偏移量访问。许多涉及某种模式识别的脚本需要在第一个小节通过分配初始值来初始化这些变量。在后续柱上执行时,脚本要么复制先前的值,要么创建新值。然后根据其他条件,脚本可能会检查当前数组条目是否仍然等于第一个数组条目,以发现数据中是否发生了有趣的事情。

以下测试脚本遵循这个简单的模式。它的唯一目的是使用扫描器来验证单个变量的第一个条目是否保留其值。

扫描在最后一个小节执行单个绘图语句。该脚本的唯一标准是测试变量保持其值并且不会被其他东西改变。在 thinkscript 中更改变量或变量数组条目是非法的,绝不应该发生。但是,此脚本显示变量的第一个条目被覆盖。这是如何发生的,如何避免?

def index;
def myVar;
if (BarNumber() == 1) {
    index = -1;
    myVar = close;
} else {
    if (close > myVar[1]) {
        myVar = close;
        index = index[1];
    } else {
        if (close <= myVar[1]) {
            index = 1;
        } else {
            index = index[1];
        }
        myVar = myVar[1];
    }
}
plot scan = GetValue(index, BarNumber() -1) == -1;

【问题讨论】:

  • 看起来和Hahn-Tech 的讨论相同,Pete 回应了 >“我承认我在编码中从未遇到过这个问题。也许是因为我习惯于以一种避免这个和其他所谓的错误......我做了很多定制项目,从来没有遇到过这个。”

标签: thinkscript


【解决方案1】:

另外,对于任何来这里寻找实际上如何创建保留其值的变量的人 - 这意味着值不会跨栏改变,因为您不希望它 - 这里有 3 种方法...

使用递归变量,如下:

方法一if statement

# declare variable without initializing
def myVar;

# set up `if` condition for which variable should be set
if ( BarNumber() == 1 ) {
  
  # set the value you want when the condition is met
  # in this case, the variable is set to the close value at bar 1
  myVar = close;
}
# thinkScript always requires an `else` 
else {

  # now, here, if you want the variable to change, enter the desired value,
  # or, if you want it to always stay the same, then...
  myVar = myVar[1];

}

方法二if expression

# same as above, really, but more compact; use parens as desired for clarity
def myVar = if BarNumber() == 1 then close else myVar[1];

方法3CompoundValue()

注意: 一般情况下,CompoundValue() 的作用与上述示例相同,因此不需要使用它;然而,它有时必须被使用。详情见下方代码。

def myVar = CompoundValue(1,
                          if BarNumber() == 2 then close[1] else myVar[1],
                          0);

plot test = myVar;

一些CompoundValue()详情

首先,参数,正如docs 中的描述可能会造成混淆:

第一个参数length。它表示柱号之后CompoundValue() 将利用第二个参数中的表达式。

1 是该参数接受的最小(和默认)值。

第二个参数"visible data"。这里的值必须是一个表达式,通常是一个计算(虽然我在这个例子中使用了if 表达式)。

这是CompoundValue() 将在BarNumber()过去 时使用的数据操作。例如,对于这个例子,因为我在第一个参数中有一个 1,所以 if 表达式在 thinkScript 处理 2nd 栏之前不会生效。在 0 和 1 小节,第 3 个参数值将发挥作用。

请注意,我在表达式中使用了 close 的偏移量。由于我必须在 length 参数中至少输入 1,因此 thinkScript 在开始使用表达式之前将位于第 2 小节。因此,我必须指出前一根柱线的收盘价 (close[1]) 确实是我想要的。

第三个参数"historical data",是将用于柱状编号的值在第一个参数中的长度(柱状编号)值之前并包括 .

在本例中,我输入了 0,但我也可以使用 Double.NaN。在我的情况下这无关紧要,因为我不关心在计算点之前设置任何值。

CompoundValue() 文档给出了一个现在很容易理解的斐波那契示例。它还说明了为什么人们可能想要为之前的柱设置一个值:

def x = CompoundValue(2, x[1] + x[2], 1);
plot FibonacciNumbers = x;

length = 2,所以第二个参数的计算在第三个柱出现之前不会发生。

"visible data" = x[1] + x[2],计算将在第二根柱之后(即从第三根柱向前)的每个柱进行。

"historical data" = 1,因此对于柱线 1 和 2,将使用常数值 1 代替第二个参数中的计算。这适用于斐波那契计算!


至于为什么要使用CompoundValue() 而不是上面的前两种方法,主要原因是在绘制具有多个长度或“偏移量”的项目时需要CompoundValue。简而言之,thinkScript 会将所有绘制的偏移量更改为等于最大偏移量。 CompoundValue 与其他绘图变量不同,保持其规定的偏移值。

详情请看thinkScript教程的Chapter 12. Past/Future Offset and Prefetch,以及我对SO问题的回答Understanding & Converting ThinkScripts CompoundValue Function

【讨论】:

    【解决方案2】:

    这是无法避免的,因为这是 2019 年 6 月 13 日扫描引擎中的缺陷和错误。让我通过几个简单的步骤提供一个证明,所有代码都由扫描引擎针对所有符号执行,以获得最大的覆盖率。

    def myLowest = LowestAll(BarNumber());
    plot scan = myLowest == 1;
    

    这将返回整个集合,并证明在扫描的所有符号中的第一个条中都有BarNumber() == 1; Always。

    再次,我们从所有符号开始

    def myHighest = HighestAll(BarNumber());
    plot scan = BarNumber() == myHighest;
    

    这会返回整个集合。

    证明在所有扫描中,单个绘图语句仅在最高柱上执行一次,而不管每个符号有多少柱。所以它自己计算HighestAll(BarNumber()),我们不需要这样做。

    有了上面,我们有了一些工具,可以利用扫描引擎本身来测试一些基本条件。这很重要,因为如果我们想识别错误,我们需要有一种可靠的方法来检查实际值与预期值。之所以如此,是因为我们无法调试扫描引擎——我们必须使用这种间接方法,一种可靠的方法。

    现在我们正在使用这些知识来测试扫描引擎是否成功执行用户编写的“if”语句。

    def index;
    if (BarNumber() == 1) {
        index = -1;
    } else {
       index = 3;
    }
        
    plot scan = GetValue(index, BarNumber() -1) == -1;
    

    GetValue() 函数允许我们根据每个符号的柱数使用可变偏移量进行索引。我们希望比较index 的第一个条目,我们可以在其中验证内容为数字-1,并且它按预期工作,因为扫描返回集合中的所有符号。

    作为最后一步,我们将扩展 if 语句的代码以显示扫描引擎故障。我们仍在 plot 语句中执行相同的测试。但是,现在扫描引擎破坏了index 的第一个条目,这是新代码的副作用。现在测试失败了。扫描引擎将indexBarNumber() == 1 的值设置为0。没有执行此操作的用户代码 - 用户代码将其设置为 -1。

    def index;
    def myVar;
    if (BarNumber() == 1) {
        index = -1;
        myVar = close;
    } else {
        if (close > myVar[1]) {
            myVar = close;
            index = index[1];
        } else {
            if (close <= myVar[1]) {
                index = 1;
            } else {
                index = index[1];
            }
            myVar = myVar[1];
        }
    }
    plot scan = GetValue(index, BarNumber() -1) == -1;
    

    因此,通过少量的小步骤,我们可以逐步显示扫描引擎存在缺陷/错误,因为它无法在变量中保留值。

    下面是另一个例子:

    def sum;
    if (BarNumber() == 1) {
        sum = 1;
    } else {
        if (BarNumber() < 5) {
            sum = sum[1] + 1;
        } else {
            sum = sum[1]; # This causes the problem.
            #sum = Double.NaN;# alternative: does not corrupt previous value but useless.
        }
    }
    plot scan = GetValue(sum, BarNumber() -1) == 1;
    

    让我们从观察到的行为中得出结论,编译器中可能会发生什么: 简而言之,thinkscript if 语句并不能防止超出范围的数组索引。这是一个缺陷。

    详情如下: 有两个条件要记住,知道图表或扫描窗口有一个左边缘,这是我们作为程序员看到的,数据似乎开始的地方:

    1. 时间序列可能在左侧图表边界之外有更多历史记录。
    2. 时间序列可能从左侧图表边界开始 - 没有更多数据。

    案例 2) 就是我们正在研究的。

    因此,如果在第 1 条我们将执行 index[1]close[1],那将因为超出范围的数组索引而失败。

    为了防止这种情况,我们使用 if-else 结构如下:

    def index
    if (BarNumber() == 1) {
        index = -1;
    }else{
        index = index[1];
    }
    

    我们必须假设if 语句的每个分支在需要时执行有效代码。 然而,我们观察到情况并非如此。我们观察到else 分支中的index[1] 术语即使在它必须失败的条件(BarNumber() == 1) 下也会被评估。 这会导致整个if 语句在if 分支中失败,此时不应在else 分支中执行任何评估。我们可以通过将术语 index[1] 替换为有效的 GetValue(index, 1) 来证明确实如此。

    【讨论】:

    • 我还没有考虑你的整个问题,@user250343;但是,def myHighest = HighestAll(BarNumber()); plot scan = BarNumber() == myHighest; 处的逻辑存在缺陷——因为循环针对每个柱运行,在柱 1 处,myHighest 将是柱 1。然后扫描将定位具有柱 1 的每个项目——即完整的结果集。但是,如果您将myHighest 的值替换为实际数字,例如3,则扫描将不会显示完整的结果集。事实上,plot 语句在 每个 栏执行;但是,在扫描中,结果只会在最后一个小节返回。
    • @leanne 根据你的逻辑,这不是一个缺陷,这是有缺陷的。您第一次断言在 bar 1 处 myHighest 将是 bar 1 是不正确的。 HighestAll() 函数的唯一目的是返回图表中的最高柱数,而不是返回当前柱(包括当前柱)的最高柱数,在您的案例柱 1 中。
    • 我的立场是正确的:HighestAll( BarNumber() ) 确实返回了最高的柱数,无论当前正在处理哪个柱。
    【解决方案3】:

    我在 2021 年 3 月 30 日对此进行测试,因此 OP 的原始问题可能已修复

    首先,为了看看 ThinkOrSwim 对这段代码做了什么,我拿了第一个例子,并把它放到了一个图表研究中。使用标签和图表气泡,我可以演示 thinkScript 处理每个条形时发生的情况。

    • 测试代码:
    # OP's example code
    def index;
    def myVar;
    if (BarNumber() == 1) {
        index = -1;
        myVar = close;
    } else {
        if (close > myVar[1]) {
            myVar = close;
            index = index[1];
        } else {
            if (close <= myVar[1]) {
                index = 1;
            } else {
                index = index[1];
            }
            myVar = myVar[1];
        }
    }
    #plot scan = GetValue(index, BarNumber() -1) == -1;
    
    
    # labels; do non-variable offset values show correct values based on code?
    def numBars = HighestAll( BarNumber() );
    AddLabel(yes, " numBars: " + numBars + " ", Color.CYAN);
    
    def barNum = if BarNumber() == 0 then 0 
                 else if BarNumber() == 1 then 1 else -6;
    AddLabel(yes,
     " Bar 1: " + "index[5]: " + index[5] + ", GetValue(index, 5): " + GetValue(index, 5) + " ",
     Color.LIGHT_ORANGE );
    AddLabel(yes,
     " Bar 2: " + "index[4]: " + index[4] + ", GetValue(index, 4): " + GetValue(index, 4) + " ",
     Color.LIGHT_ORANGE );
    AddLabel(yes,
     " Bar 3: " + "index[3]: " + index[3] + ", GetValue(index, 3): " + GetValue(index, 3) + " ",
     Color.LIGHT_ORANGE );
    AddLabel(yes,
     " Bar 4: " + "index[2]: " + index[2] + ", GetValue(index, 2): " + GetValue(index, 2) + " ",
     Color.LIGHT_ORANGE );
    AddLabel(yes,
     " Bar 5: " + "index[1]: " + index[1] + ", GetValue(index, 1): " + GetValue(index, 1) + " ",
     Color.LIGHT_ORANGE );
    AddLabel(yes,
     " Bar 6: " + "index[0]: " + index[0] + ", GetValue(index, 0): " + GetValue(index, 0) + " ",
     Color.LIGHT_ORANGE );
    
    
    # chart bubbles; displaying variable values - are they what we expect?
    AddChartBubble(yes, high,
      "Bar Number: " + BarNumber() + "\nclose: " + close + "\nmyVar: " + myVar + "\nindex: " + index, 
      Color.YELLOW, if BarNumber() % 2 == 0 then no else yes);
    
    # yes! the first entry of both variables actually remain the same
    AddChartBubble(yes, low,
      "BarNumber() -1 == " + (BarNumber() -1) + "\nGetValue(index, " + (BarNumber() -1) + ") == " + GetValue(index, BarNumber() -1) + "\nGetValue(myVar, " + (BarNumber() -1) + ") == " + GetValue(myVar, BarNumber() -1), 
      Color.YELLOW,  if BarNumber() % 2 == 0 then yes else no);
    
    

    我将图表设置为显示 6 个(每日)柱状图,因此结果很容易看到。

    • 以下是 2021 年 3 月 19 日至 2021 年 3 月 26 日期间 TRCH 图表上的结果:

    可以看到,第一条中的 both indexmyVar 的值确实保持了它们的值。 在处理每个条形图。


    现在测试扫描:扫描函数是一个过滤器,它返回任何满足编码条件的股票。对于扫描,我们可以创建一个显示指定值的列,而不是基于返回的结果数量进行测试。

    • 所以,首先我创建了一个专门只包含示例代码的研究:
    def index;
    def myVar;
    if (BarNumber() == 1) {
        index = -1;
        myVar = close;
    } else {
        if (close > myVar[1]) {
            myVar = close;
            index = index[1];
        } else {
            if (close <= myVar[1]) {
                index = 1;
            } else {
                index = index[1];
            }
            myVar = myVar[1];
        }
    }
    plot scan = GetValue(index, BarNumber() -1);
    
    

    • 然后我根据该研究创建了一个自定义列。当我添加指向自定义研究的绘图时,thinkScript 将拉入当前代码并设置列代码。在这种情况下完成的列代码如下所示:
    script SO_ScanProofOfConcept_Column {
    def index;
    def myVar;
    if (BarNumber() == 1) {
        index = -1;
        myVar = close;
    } else {
        if (close > myVar[1]) {
            myVar = close;
            index = index[1];
        } else {
            if (close <= myVar[1]) {
                index = 1;
            } else {
                index = index[1];
            }
            myVar = myVar[1];
        }
    }
    plot scan = GetValue(index, BarNumber() -1);
    
    }
    plot scan = SO_ScanProofOfConcept_Column().scan;
    

    现在,我可以看到 自定义列中的所有内容实际上都是 -1.0。浏览左起第 4 列,从“Custom04-...”开始:


    因此,至少截至该日期,扫描引擎仍如我们预期的那样保留变量的值。数据数组中的数据似乎没有损坏。 p>

    【讨论】: