본문 바로가기
Algorithm Trading/Pine Script v5 기본

Pine Script v5 기본 문법 - 2 (조건문, 반복문)

by 컴돈AI 2023. 12. 25.

목차

    Conditional structures

    Introduction

    • Pine Script에서 사용하는 조건문은 if와 switch가 있습니다.
    • Pine Script에서는 조건문의 local block에서 호출하지 못하는 내장함수가 있습니다.
      • alertcondition(), barcolor(), fill(), hline(), indicator(), library(), plot(), plotbar(), plotcandle(), plotchar(), plotshape(), 
      • 예를 들어 다음 코드는 에러가 발생합니다.
        • //@version=5
          indicator("", "", true)
          float v = 0
          
          if close>open
              plot(v)
          else
              plot(v+1)
        • plot은 조건문의 local block에서 호출하지 못합니다.

    if 문

    • if 문 형식
      • if <expression>
            <local_block>
        {else if <expression>
            <local_block>}
        [else
            <local_block>]
        • { } 로 묶인 부분은 0번 이상 반복될 수 있고, [ ] 로 묶인 부분은 0번 또는 1번 등장할 수 있습니다.
        • expression은 bool 유형이거나 해당 유형으로 자동 형변환이 가능해야합니다.
    • if를 사용해서 value를 return 받기
      • [<declaration_mode>] [<type>] <identifier> = if <expression>
            <local_block>
        {else if <expression>
            <local_block>}
        [else
            <local_block>]
    • 만약 다음과 같이 else가 생략된 경우에는 else로 갈 경우 na를 return 합니다.
      • x = if close > open
            close
    •  중첩된 if 문 사용도 가능합니다.
      • if condition1
            if condition2
                if condition3
                    expression
        하지만 보통 이렇게 중첩하는 것은 성능 측면에서 권장되지 않습니다. 가능하다면 아래와 같이 한 번에 작성하는 것이 더 좋은 선택입니다.
      • if condition1 and condition2 and condition3
            expression

    switch 문

    • swtich 문은 두 가지 형태로 존재합니다.
      • [[<declaration_mode>] [<type>] <identifier> = ]switch <expression>
            {<expression> => <local_block>}
            => <local_block>
      • [[<declaration_mode>] [<type>] <identifier> = ]switch
            {<expression> => <local_block>}
            => <local_block>
        • { } 로 묶인 부분은 0번 이상 반복될 수 있고, [ ] 로 묶인 부분은 0번 또는 1번 등장할 수 있습니다.
    • switch with an expression 예시
      • //@version=5
        indicator("Switch using an expression", "", true)
        
        string maType = input.string("EMA", "MA type", options = ["EMA", "SMA", "RMA", "WMA"])
        int maLength = input.int(10, "MA length", minval = 2)
        
        float ma = switch maType
            "EMA" => ta.ema(close, maLength)
            "SMA" => ta.sma(close, maLength)
            "RMA" => ta.rma(close, maLength)
            "WMA" => ta.wma(close, maLength)
            =>
                runtime.error("No matching MA type found.")
                또는 float(na)
        
        plot(ma)
         
        •   조건에 만족하는 것이 없을 경우 runtime.error를 발생시키게 하거나, na를 반환시킬수 있습니다. 다른 리턴값하고 타입을 통일시 해주기 위해 float를 지정해줍니다.
    • switch without an expression
      • //@version=5
        strategy("Switch without an expression", "", true)
        
        bool longCondition  = ta.crossover( ta.sma(close, 14), ta.sma(close, 28))
        bool shortCondition = ta.crossunder(ta.sma(close, 14), ta.sma(close, 28))
        
        switch
            longCondition  => strategy.entry("Long ID", strategy.long)
            shortCondition => strategy.entry("Short ID", strategy.short)
        • 여러 케이스 중에서 첫번째로 true가 되는 조건을 만족하는 케이스만 실행합니다.
        • 즉, 다음과 같은 순서로 실행됩니다.
          1. longCondition이 true 인 경우: switch 문은 longCondition에 대한 케이스를 실행하고 (strategy.entry("Long ID", strategy.long)), 이후의 케이스는 무시합니다. 즉, shortCondition에 대한 케이스는 체크하지 않습니다.
          2. longCondition이 false이고 shortCondition이 true인 경우: switch 문은 shortCondition에 대한 케이스를 실행합니다 (strategy.entry("Short ID", strategy.short)).
          3. 두 조건 모두 false인 경우: 어떤 케이스도 실행되지 않습니다.

    Matching local block type requirement

    • 여러 로컬 블록이 구조에 사용되는 경우 모든 로컬 블록의 반환 값 유형이 일치해야 합니다.
      • 이는 선언에서 변수에 값을 할당하기 위해 구조를 사용하는 경우에만 적용됩니다.
      • 변수는 하나의 유형만 가질 수 있고 명령문이 해당 분기에서 호환되지 않는 두 유형을 반환하는 경우 변수 유형을 제대로 결정할 수 없기 때문입니다.
    • 정상적으로 작동하는 코드
      • x = if close > open
            close
        else
            open
    • 컴파일 오류 코드
      • // Compilation error!
        x = if close > open
            close
        else
            "open"

    Loops

    Introduction

    • Pine Script에서는 다양한 내장함수들이 존재하여 많은 상황에서 loop는 불필요 합니다. 
    • 불필요한 예시
      • 평균 구하기
        • 잘못된 방법
          • //@version=5
            indicator("Inefficient MA", "", true)
            MA_LENGTH = 10
            sumOfCloses = 0.0
            for offset = 0 to MA_LENGTH - 1
                sumOfCloses := sumOfCloses + close[offset]
            inefficientMA = sumOfCloses / MA_LENGTH
            plot(inefficientMA)
        • 올바른 방법
          • //@version=5
            indicator("Efficient MA", "", true)
            thePineMA = ta.sma(close, 10)
            plot(thePineMA)
          • ta.sma를 이용하면 손쉽게 이동평균선을 계산할 수 있습니다.
      • 마지막 10개 막대에서 상승 막대수 계산하기
        • 잘못된 방법
          • //@version=5
            indicator("Inefficient sum")
            MA_LENGTH = 10
            upBars = 0.0
            for offset = 0 to MA_LENGTH - 1
                if close[offset] > open[offset]
                    upBars := upBars + 1
            plot(upBars)
             
        • 올바른 방법
          • //@version=5
            indicator("Efficient sum")
            upBars = math.sum(close > open ? 1 : 0, 10)
            plot(upBars)
            • math.sum(source,length) 를 지정해주면 지정된 개수만큼의 막대에 대해서 source를 더해줍니다.
            • 여기서 close > open 이면 1 아니면 0이기 때문에 각각의 10개의 막대에 대해서 계산한뒤 더해주게 됩니다.
    • 이처럼 생각보다 loop가 필요한 경우는 거의 없습니다. 그렇다면 언제 필요할까요? (필요한 예시)
      • arrays,matrices, maps 등을 조작할 때
      • 현재 바에서만 알 수 있는 기준값을 사용하여 바를 분석하기 위해 과거를 되돌아 보는 것(내장된 함수를 사용하여 수행할 수 없는 과거 막대에 대한 계산을 수행하고 싶은 경우)
        • 예를 들면 현재 바의 고점보다 과거 고점이 몇 개나 높은지 확인
        • 현재 막대의 최고치는 스크립트가 실행 중인 막대에서만 알려져 있으므로 시간을 거슬러 올라가 과거 막대를 분석하려면 루프가 필요합니다.
        • 여기서 잠깐 헷갈릴 수 있는 부분이 존재합니다. 이것도 그냥 math.sum으로 아래처럼 하면 되는거 아니야? 할 수 있습니다. 하지만 math.sum은 이제 막대개수만큼 sliding 방식으로 체크하는 방식이기 때문에 이제 시점을 과거로 옮겨서 그때 v>high를 체크하게 됩니다. v 자체는 이제 high이기 때문에 결국엔 high>high가 되는 것이게 되므로 아래 코드는 틀린 코드가 되는 것입니다. 결국 현재 막대에 대한 정보와 과거 막대와 비교하기 위해서는 loop가 반드시 필요하게 됩니다.
          • //@version=5
            indicator("Efficient sum")
            v = high
            
            upBars = math.sum(v >high ? 1 : 0, 10)
            plot(upBars)

    for 문

    • 구조
      • [[<declaration_mode>] [<type>] <identifier> = ]for <identifier> = <expression> to <expression>[ by <expression>]
            <local_block_loop>
        • { } 로 묶인 부분은 0번 이상 반복될 수 있고, [ ] 로 묶인 부분은 0번 또는 1번 등장할 수 있습니다.
        • by <expression>은 루프가 반복될 때마다 루프 카운터가 증가하거나 감소하는 단계입니다. 시작값< 끝값 이면 기본값은 1이고 시작값>끝값 이면 기본값은 -1입니다.
    • 예시(현재 막대의 고점보다 고점이 높았던 막대의 개수 구하기)
      • //@version=5
        indicator("High Bars Count", overlay=true)
        
        // 현재 막대의 고점
        currentHigh = high
        
        // 과거 10개 막대 중 현재 고점보다 높은 막대의 수를 계산
        var int higherHighsCount = na
        higherHighsCount := 0
        for i = 1 to 10
            if high[i] > currentHigh
                higherHighsCount := higherHighsCount + 1
        
        plot(higherHighsCount, title="Higher Highs Count")

    while 문

    • 구조
      • [[<declaration_mode>] [<type>] <identifier> = ]while <expression>
            <local_block_loop>
        • { } 로 묶인 부분은 0번 이상 반복될 수 있고, [ ] 로 묶인 부분은 0번 또는 1번 등장할 수 있습니다.
    • 예시(for문 예시를 바꿔, 더 이상 현재 고점보다 높은 고점이 없을때까지의 막대 개수 계산하기)
      • //@version=5
        indicator("Count of Higher Highs Using While Loop", overlay=true)
        
        // 현재 막대의 고점
        currentHigh = high
        
        // 현재 고점보다 높은 고점을 가진 이전 막대들의 수를 계산
        int higherHighsCount = 0
        int i = 1
        
        while (i < 100 and high[i] > currentHigh)
            higherHighsCount := higherHighsCount + 1
            i := i + 1
        
        plot(higherHighsCount, title="Higher Highs Count Using While Loop")
        • i<100은 무한루프를 방지하기 위함입니다.
    •  return 값이 있는 while 문 예시
      • //@version=5
        indicator("")
        int n = input.int(10, "Factorial of", minval=0)
        
        factorial(int val = na) =>
            int counter = val
            int fact = 1
            result = while counter > 0
                fact := fact * counter
                counter := counter - 1
                fact
        
        // Only evaluate the function on the first bar.
        var answer = factorial(n)
        plot(answer)