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

Pine Script v5 기본 문법 - 1 (스크립트 기본, 연산자, 변수선언)

by 컴돈AI 2023. 12. 24.

목차

    Script structure

    • Pine Script는 아래와 같은 구조를 따릅니다.
      • <version>
        <declaration_statement>
        <code>

    Version

    • 다음 형식의 컴파일러 주석은 스크립트가 작성된 Pine Script 버전을 컴파일러에게 알려줍니다.
      • //@version=5
      • 현재는 Pine Script v5이기 때문에 위와 같이 작성합니다. 
        • 작성하지 않고 생략한다면 버전1로 간주됩니다. 따라서 필수적으로 작성해주어야합니다.
    • 커뮤니티 코드들을 보면 다른 버전의 코드들도 있기때문에 이를 생각하면서 코드를 확인해야합니다.
      • 만약 현재 버전보다 낮은 코드들이 있다면 스크립트 우측 상단에 ...을 누른뒤 v5으로 컨버트하기를 눌러주게 되면 코드 버전이 업데이트 된 버전으로 나타냐게 됩니다.

    Declaration statement

    • 모든 Pine Script는 다음 함수 중 하나를 호출하는 하나의 declaration statement를 포함해야합니다.
      • indicator()
      • strategy()
      • library()
      • 이는 스크립트 유형을 식별하는데 사용됩니다. 스크립트 유형에 따라 다르기때문에 반드시 작성해주어야 합니다.
    • 또한 다양한 입력인자들이 존재하여 초기 설정등을 진행할 수 있습니다.
      • indicator(title, shorttitle, overlay, format, precision, scale, max_bars_back, timeframe, timeframe_gaps, explicit_plot_zorder, max_lines_count, max_labels_count, max_boxes_count, max_polylines_count)
      • strategy(title, shorttitle, overlay, format, precision, scale, pyramiding, calc_on_order_fills, calc_on_every_tick, max_bars_back, backtest_fill_limits_assumption, default_qty_type, default_qty_value, initial_capital, currency, slippage, commission_type, commission_value, process_orders_on_close, close_entries_rule, margin_long, margin_short, explicit_plot_zorder, max_lines_count, max_labels_count, max_boxes_count, risk_free_rate, use_bar_magnifier, max_polylines_count)
      • library(title, overlay) → void
    • 각 스크립트 유형별로 고유한 요구 사항이 존재합니다. 
      • 지표(Indicator)의 경우 차트에 출력을 생성하는 함수 호출(ex. plot(), plotshape(), barcolor(), line 등)을 하나이상 포함해야 합니다.
      • 전략(Strategy)의 경우 strategy.*() 호출이 하나 이상 포함되어야 합니다. (ex. strategy.entry())
      • 라이브러리(Libaryr)의 경우 내보낸 함수 또는 사용자 정의 유형이 하나 이상 포함되어 있어야 합니다.

    Code

    • 이 부분부터 이제 코드를 작성하는 부분입니다. 프로그래밍 하듯이 Pine Script 코드를 작성해주시면 됩니다.
    • 대부분 파이썬과 코드 규칙은 비슷하지만 몇몇 다른 부분을 살펴보도록 하겠습니다.
      • global scope에서는 띄어쓰기 없이 바로 코드를 작성하지만 local scope에서는 탭이나 4번 띄어쓰기를 이용해야합니다. 
        • //@version=5
          
          indicator("", "", true)    // Declaration statement (global scope)
          
          barIsUp() =>    // Function declaration (global scope)
              close > open    // Local block (local scope)
          
          plotColor = if barIsUp()  // Variable declaration (global scope)
              color.green     // Local block (local scope)
          else
              color.red       // Local block (local scope)
          
          bgcolor(color.new(plotColor, 70))   // Call to a built-in function  (global scope)
      • , (쉼표)를 이용하여 여러 줄의 코드를 한줄로 작성할 수 있습니다.
        • plot(bar_index,   "Bar index",    color = color.green), plot(customIndex, "Custom index", color = color.red, style = plot.style_cross)
           
        • a=1, b=2, c=3
      • 코드가 길어서 한 눈에 안들어올 경우에는 다음줄에서 이어서 작성하는데 대신 한칸을 띄어주고 작성합니다. (꼭 한 칸이 아니여도 됩니다. 4의 공백만 아니라면 문제 없습니다. 즉 두칸이나 세칸을 띄는것도 가능합니다.)
        • indicator("My 
           script")
        • a = open + high + low + close
          a = open +
                high +
                    low +
                       close
        • plot(ta.correlation(src, ovr, length),
             color = color.new(color.purple, 40),
             style = plot.style_area,
             trackprice = true)
        • 만약 기본적으로 4칸을 들여써야하는 경우라면 그거보다 한칸을 더 띄어서 5칸을 띄우면 됩니다.
      • 주석의 경우 // 를 작성한 뒤 입력하면 됩니다. 
        • 많은 내용을 한 번에 주석처리를 하고 싶을 경우, 드래그 후에 ctrl + / 를 해주면 됩니다.
      • 주석의 경우, 특별한 기능을 가진 주석들이 있습니다. 
        • //@version 
          • 사용할 PineScript 버전을 지정합니다. (위에서 설명한 내용)
        • //@description 
          • library() 선언문을 사용하는 스크립트에 대한 사용자 정의 설명을 설정합니다.
          • 단순히 주석과 같은 기능이지만, library에서 가독성을 위해 따로 표시해줍니다.
        • //@function, //@param, //@returns
          • 사용자 정의 함수, 그 매개변수, 그리고 결과에 대한 사용자 정의 설명을 추가합니다. 이들은 함수 선언 위에 위치해야 합니다.
          • 딱히 작성해주지 않더라도 함수가 작동하는데 아무런 문제가 없지만 함수에 대해 설명을 해주면 가독성이 향상됩니다.
        • //@type 및 //@field
          • 사용자 정의 타입(UDT) 및 그 필드에 대한 사용자 정의 설명을 추가합니다. 이들은 타입 선언 위에 위치해야 합니다.
          • type과 field는 일종의 클래스와 그 안에 있는 변수같은 존재입니다.
            • 더보기
              // @type            Used to represent the coordinates and color to draw a triangle.
              // @field time1     Time of first point.
              // @field time2     Time of second point.
              // @field time3     Time of third point.
              // @field price1    Price of first point.
              // @field price2    Price of second point.
              // @field price3    Price of third point.
              // @field lineColor Color to be used to draw the triangle lines.
              type Triangle
                  int   time1
                  int   time2
                  int   time3
                  float price1
                  float price2
                  float price3
                  color lineColor
                  
              //@function Draws a triangle using the coordinates of the `t` object.
              //@param t  (Triangle) Object representing the triangle to be drawn.
              //@returns  The ID of the last line drawn.
              drawTriangle(Triangle t) =>
                  line.new(t.time1, t.price1, t.time2, t.price2, xloc = xloc.bar_time, color = t.lineColor)
                  line.new(t.time2, t.price2, t.time3, t.price3, xloc = xloc.bar_time, color = t.lineColor)
                  line.new(t.time1, t.price1, t.time3, t.price3, xloc = xloc.bar_time, color = t.lineColor)
              
              // Draw the triangle only once on the last historical bar.
              if barstate.islastconfirmedhistory
                  //@variable Used to hold the Triangle object to be drawn.
                  Triangle triangle = Triangle.new()
              
                  triangle.time1  := x1Input
                  triangle.time2  := x2Input
                  triangle.time3  := x3Input
                  triangle.price1 := y1Input
                  triangle.price2 := y2Input
                  triangle.price3 := y3Input
                  triangle.lineColor := color.purple
              
                  drawTriangle(triangle)
          • // 로 그냥 표시해도 딱히 문제가 되지 않습니다.
        • //@variable
          • 변수 선언 위에 위치시켜 해당 변수에 대한 사용자 정의 설명을 추가합니다.
          • // 로 그냥 표시해도 딱히 문제가 되지 않습니다.
        • //@strategy_alert_message
          • 전략 스크립트에 대해 경고 생성 대화 상자에서 "메시지" 필드를 사전 채우기 위한 기본 메시지를 제공합니다.
          • 예를 들어 //@strategy_alert_message {{strategy.order.alert_message}} 처럼 지정해두면, strategy.entry()의 입력인자중 alert_message가 있는데 여기에 해당하는 alert_message가 alert_message로 전송되게 됩니다.
            • 즉 실시간 매매에서 매매신호가 발생할때마다 발생되는 메시지를 alert_message에 지정된 값을 보내줄 수 있습니다.
        • //#region and //#endregion
          • 이 주석을 사용하면 Pine Editor에서 접을 수 있는 코드 영역을 생성합니다.

    Identifiers

    • 변수나 함수 이름은 반드시 대문자(A-Z)나 소문자(a-z)나 밑줄(_)로 시작해야 합니다.
      • 다음 문자부터는 문자,숫자,밑줄 모두 들어갈 수 있습니다.
    • 변수나 함수 이름은 대소문자를 구분합니다.
    • 예시
      • myVar
      • _myVar
      • my123Var
      • functionName
      • MAX_LEN
      • max_len
      • maxLen
      • 3barsDown  -> 안되는 경우 입니다!
    • constant(변하지 않는 값)같은 경우는 SNAKE_CASE(대문자)를 권장하고, 다른 식별자(변수나 함수명)에는 camelCase를 사용하는 것을 권장합니다.
      • SNAKE_CASE
        • 상수에 사용되며, 모든 글자를 대문자로 작성하고 단어 사이에 밑줄(_)을 사용합니다.
        • ex : MAX_LIMIT , DEFAULT_VALUE
      • camelCase
        • 변수나 함수 이름에 사용되며, 첫 단어는 소문자로 시작하고 이후의 각 단어의 첫 글자는 대문자로 작성합니다.
        • ex : startingValue, fastLength

    Operators

    Arthmetic operators(산술 연산자)

    • 종류
      • + : 덧셈과 문자열 연결
      • - : 뺄셈
      • * : 곱셈
      • / : 나누기
      • % : 나누기 후 나머지
    • 두개의 피연산자 타입에 따른 결과 타입
      • int 와 float -> float
      • int 와 int -> int
      • na가 하나라도 있으면 -> na

    Comparison operators(비교 연산자)

    • 종류
      • < : 작다
      • <= : 작거나 같다
      • != : 같지 않다
      • == : 같다
      • > : 크다
      • >= : 크거나 같다
    • 두개의 피연산자 타입에 따른 결과 타입
      • 모두 숫자일 경우 -> bool (true or false)
      • na가 하나라도 있으면 -> na

    Logical operators(논리 연산자)

    • not : 부정(단항 연산자입니다. 즉 하나의 피연산자가 존재)
    • and 
    • or

    '?:'  ternary operator (삼항 연산자)

    • 삼항 연산자는 다음과 같이 작성됩니다.
      • condition ? valueWhenConditionIsTrue : valueWhenConditionIsFalse
      • condition이 true이면 valueWhenConditionIsTrue를 반환하고, false(또는 na)이면 valueWhenConditionIsFalse를 반환합니다.
    • 아래 예시처럼 삼항 연산자를 조합해서 사용이 가능합니다.
      • timeframe.isintraday ? color.red : timeframe.isdaily ? color.green : timeframe.ismonthly ? color.blue : na
      • timeframe.isintraday가 true이면 color.red가 반환되고, false일 경우 timeframe.isdaily로 넘어가게 됩니다.
      • 다시 timeframe.isdaily가 true이면 color.green이 반환되고, false이면 timeframe.ismonthly로 넘어가게 됩니다.
      • 또 다시 timeframe.ismonthly가 true이면 color.blue가 반환되고, false이면 na가 반환됩니다.

    '[ ]' history-referencing operator (기록 참조 연산자)

    • [ ] 를 사용하면 time series의 과거 값을 참조할 수 있습니다.
      • 여기서 과거 값은 스크립트가 현재 실행 중인 막대, 즉 현재 막대 앞의 막대에 있는 변수의 값입니다.
      • 만약 open[1]이라고 한다면 이전 막대의 open 값을 현재 bar에서 가져와서 사용할 수 있습니다.
        • open[5]이면 5개전 막대의 open 값을 가져오는 것입니다. 
        • close[0]이면 현재 막대의 close값을 의미합니다.
        • 만약 해당하는 데이터가 존재하지 않는다면 na를 반환합니다. 
          • 이러한 경우를 조심해야합니다. 앞에 값들을 사용하는 경우라면 특정 bar 이후에 실행하거나 nz를 사용하여 처리를 해줘야합니다. (nz는 그 값이 nan일 경우 0이나 지정한 값으로 반환해주는 함수입니다.)
          • close > nz(close[1], open)
          • 즉, 데이터세트의 제일 첫부분만 조심해주면 됩니다. 아니면 백테스팅 시작지점을 제일 처음이 아닌 데이터세트가 수집된지 조금 지난시점 이후부터 테스트를 진행하는것도 하나의 방법입니다.
      • 응용해서 사용할 경우 다음처럼 사용해볼수도 있습니다.
        • highhest를 이용하여 지난 10개의 바의 최고점을 알 수 있습니다. 이를 이용해서 현재 가격이 지난 봉 10개의 종가중 최대값을 돌파했는지 체크해볼 수 있습니다.
        • breach = close > highest(close, 10)[1]
          • [1]을 지정해줘야지 현재봉을 포함하지 않고 이전봉부터 10개의 데이터중 가장 종가가 높은 값을 가져올 수 있습니다. 
          • 만약 [1]을 지정해주지 않는다면 close가 계속해서 변화하기 때문에 만약 현재봉이 고점을 돌파했다고 할 경우 close = highest(close,10)이 되게 됩니다. 
        • 즉, 정리하면 [ ] 는 과거데이터에서 스크립트를 통해 계산된 값을 가져오는 것입니다.
    • Pine Script에서는 스크립트가 실행되는 바의 번호를 나타내는 변수 bar_index가 있습니다. 
      • 첫번째 막대에서 bar_index는 0이고, 다음 막대로 갈수록 1씩 증가하게 됩니다.
      • 따라서 마지막 막대에서 bar_index는 데이터세트의 막대 수에서 1을 뺀것과 같습니다.
      • 이를 사용해 특정 바 이후부터 특정 계산을 적용하거나 로직을 적용하도록 할 수 있습니다. 

    Operator precedence(연산자 우선순위)

    • 우선순위
      • [ ]
      • 단항 +, 단항 -, not 
      • *, /, %
      • +, -
      • >, <, >=, <=
      • ==, !=
      • and
      • or
      • ?:
    • 위에 있는것들이 아래 있는 것들보다 먼저 실행되게 됩니다. 즉 ?:가 가장 나중에 실행되고 [ ] 가 가장 먼저 실행됩니다.
    • 동일한 우선순위인 경우에는 왼쪽에서 오른쪽으로 계산됩니다.

    '=' assignment operator(할당 연산자)

    • = 는 변수가 초기화되거나 선언될 때, 즉 처음 사용할 때 변수를 할당하는데 사용됩니다. 
    • var 로 변수를 선언할 경우 그 변수를 선언하는 코드는 첫번째 막대에서만 실행됩니다.

    ':=' reassignment operator(재할당 연산자)

    • := 는 기존 변수에 값을 다시 할당하는데 사용됩니다. 
    • 처음 선언된 후 :=를 사용하여 다시 할당된 변수를 가변 변수라고 합니다. 이러한 변수는 var을 이용하여 표현해야합니다.
    • 예시1
      • //@version=5
        indicator("= test", overlay=true)
        
        start_time = input.time(timestamp("2023-12-24T00:00:00+09:00"))
        end_time = input.time(timestamp("2099-01-01T00:00:00+09:00"))
        
        var float v = 0
        
        if (time >= start_time and time <= end_time)
            v := v+1
        
        plot(v, color=color.blue)
      • var로 선언된 v는 첫번째 스크립트가 실행되는 bar에서만 v=0 코드가 실행되고 다음 막대에서부터는 실행되지 않습니다. 계속해서 이전값을 유지하면서 1씩 증가하는 것을 확인할 수 있습니다.
    • 예시2
      • //@version=5
        indicator("", "", true)
        // Declare `pHi` and initilize it on the first bar only.
        var float pHi = na
        // Reassign a value to `pHi`
        pHi := nz(ta.pivothigh(5, 5), pHi)
        plot(pHi)
      • plot에서 nan 값이 존재할 경우에는 그냥 이전값에서 다음값이 생성되는 부분을 그냥 이어주게 됩니다. 이러한것을 방지하기 위해 값이 존재했던 이전값들을 유지하기 위해서는 nz 같은 함수를 이용하여 위처럼 표현할 수 있습니다.
      • 만약 nz를 사용하지 않을 경우 다음과 같이 표현됩니다.
        • //@version=5
          indicator("", "", true)
          
          plot(ta.pivothigh(5, 5))
        • 값이 존재하지 않는 부분에서는 그냥 값이 존재하는 부분을 그대로 이어줍니다.

    Variable declarations

    Introduction

    • 변수는 값을 보유하는 식별자입니다. 
    • 다음과 같은 형태로 표현이 가능합니다.
      • [<declaration_mode>] [<type>] <identifier> = <expression> | <structure>
        • | 는 or를 의미합니다.
        • [ ] 로 둘러쌓인 부분은 선택적으로 사용이 가능합니다. (있을수도 있고, 없을수도 있습니다.)
        • declaration_mode는 var,varip 또는 아무것도 없을수 있습니다.
        • type은 이제 변수 타입, 즉 float, int, bool, string 와 같은 것을 의미합니다.
        • identifier는 변수 이름을 작성해주면 됩니다.
        • expression에는 이제 문자나 변수 표현식 또는 함수 호출(function call)등이 사용될 수 있습니다.
        • structure은 if, for, while, switch 와 같은 구조가 사용됩니다.
    • 예시
      • float f = 10.5  
      • var barRange = float(na)
        var firstBarOpen = open
        varip float lastClose = na

    Initialization with 'na'

    • 원래같은 경우는 특정 값으로 초기화하면 자동으로 값을 보고 타입을 추측할 수 있지만, na로 초기화할때는 na가 어떤 타입인지 유추할수 없기 때문에 반드시 타입을 지정해주어야합니다. 
    • 예시
      • baseLine0 = na          // compile time error!
      • float baseLine1 = na    // OK
      • baseLine2 = float(na)   // OK

    Tuple declarations

    • <tuple_declaration> = <function_call> | <structure>
    • tuple_declaration은 리스트로 여러 변수를 묶어서 받을 수 있습니다.
      • [ma, upperBand, lowerBand] = 3개의 결과를 반환하는 어떤 함수()
      • [bbMiddle, bbUpper, bbLower] = ta.bb(close, 5, 4)
      • [macdLine, signalLine, histLine] = ta.macd(close, 12, 26, 9)

    Declaration modes

    • Declaration modes에 들어갈 수 있는 값은 var, varip 또는 아무것도 지정안하는 것입니다.
      • 아무것도 지정안한경우 로컬(local) 변수로 동작하여서 변수는 매 bar마다 지정된 값으로 초기화하게 됩니다.
        • 즉 만약 v = 0 이라고 초기화한다면, 매 막대마다 스크립트가 실행될텐데 그때마다 v=0 이라고 다시 초기화가 되는 것입니다.
        • var 이나 varip인 경우는 첫번째 막대에서만 v=0으로 초기화 하고 다음부터 그 초기화 코드는 작동하지 않습니다.
      • var이나 varip으로 지정하게 되면 초기화하는 코드는 데이터세트에서 제일 처음 바에서만 한번만 초기화되게 됩니다.
        • var과 varip 의 차이점
          • var과 varip는 백테스팅에서는 동일하게 작동하지만 실시간 매매에서는 다르게 동작합니다.
          • var의 경우는 실시간 매매에서는 하나의 bar에 대해서 값의 업데이트가 딱 한번만 진행되게 됩니다. 하지만 varip의 경우는 하나의 bar에서도 거래량이나 현재가격이 변화하면 해당 변수의 값이 계속해서 업데이트를 진행하게 됩니다.
        • var과 varip 의 차이 예시
          •  var
            • //@version=5
              indicator("var")
              var int v = -1
              v := v + 1
              plot(v)
            • var의 경우 실시간 봉에 대해서 해당하는 변수는 처음 딱 봉이 생성될때 값이 업데이트 되고 그 다음 봉이 생길때까지 값이 업데이트 되지 않습니다.
            • 하지만 if문등 조건이 있는 경우 그 조건에 맞게 변할때마다 var 변수더라도 그 조건에 맞는 값으로 변화하게 됩니다.
            • 예를 들어 아래 코드와 같을때 var 변수라고 하더라도 처음 봉이 만들어질때 평가된 if문 조건이 아닌 계속해서 가격이 변화할때마다 if문을 체크하고 다른 조건이 되면 그 값으로 표시되게 됩니다.
              • (아래 두 코드를 비교하면 varip와 var을 정확하게 이해할 수 있을것입니다.)
                • 과거 데이터에 대해서는 동일하게 작동하지만 실시간 데이터에서는 하나의 봉에서 가격이 변화할때마다 var인 경우는 if문중에서 조건에 맞는 결과를 표시하고 varip인 경우 가격이 변화할때마다 조건문에 맞는 결과를 실행하게 됩니다.
                • 즉 varip는 중복해서 값이 더해지고, var은 값이 하나의 봉에서 딱 한번만 변화하게 됩니다.(근데 그 변화가 1이 더해질지 2가 더해질지 close가 open보다 큰지 작은지에 따라서 실시간으로 변화하게되는것입니다.)
              • //@version=5
                indicator("", "", true)
                var float v = 0
                
                if close>open
                    v:=v+1
                else
                    v:=v+2
                
                plot(v)
              • //@version=5
                indicator("", "", true)
                varip float v = 0
                
                if close>open
                    v:=v+1
                else
                    v:=v+2
                
                plot(v)
          • varip
            • //@version=5
              indicator("varip")
              varip int v = -1
              v := v + 1
              plot(v) 
            • 과거에서는 var과 동일하게 작동하지만 실시간 봉에서는 틱이 바뀔때마다 계속해서 v가 같이 업데이트 되는것을 확인할 수 있습니다.