본문 바로가기
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)