이번에 살펴볼 지표는 마크 존슨(Mark Johnson)이라는 사람이 만든 PGO(Pretty Good Oscillator), 제법 좋은 오실레이터이다. PGO는 종가와 단순 이동 평균(SMA, Simple Moving Average)의 차이를 ATR(Average True Range)의 지수 이동 평균(EMA, Exponential Moving Average)으로 나눈 값을 표현하는 지표다. 즉 이동 평균과 현재 가격의 괴리를 측정하는 지표로 이 괴리를 최근 변동성, 즉 ATR로 나눠 주가 변동성을 반영한 현재 가격의 위치를 표현하도록 설계되었다.
통상 이동 평균이던 ATR이던 14일을 기준으로 사용하나, ATR 자체가 이동평균을 적용한 개념이라 ATR은 지수 이동 평균 하지 않고 바로 사용하는 경우도 있다. 이름처럼 변동성을 감안하여 주가의 위치를 파악하는데 제법 좋은 지표로 잘 사용하면 주가의 현재 위치 및 과매수, 과매도 구간을 파악하고 모멘텀을 측정하는데 유용하게 사용될 수 있다.
매매전략은 0을 기준으로 0을 상향 돌파하면 매수, 0을 하향 돌파하면 매도하는 전략이 사용될 수 있다. 단, 이경우는 허위 시그널이 많이 발생할 수 있으며, 매매 빈도가 잦아져 거래비용이 많이 나오는 단점이 있다. 이런 단점을 완화하기 위해서는 구간을 길게 잡으면 해소가 되긴 하나 시그널이 늦게 발생하게 되는 단점 역시 동반된다.
가장 많이 사용되는 매매법은 상, 하단에 밴드를 설정하고 하단 밴드를 상향 돌파하면 매수, 상단 밴드를 하향 돌파하면 매도하는 전략이다. 이 경우, 통상 -2에서 2까지의 구간을 설정하고 -2를 상향 돌파하면 매수하고, 2를 하향 돌파하면 매도하는 게 일반적으로 사용된다. PGO는 이동 평균과 주가의 괴리를 ATR로 나눠주기 때문에 변동성을 감안하여 지표가 산출된다. 때문에 가격이 급등한다고 해도 이동평균과 주가의 괴리가 상당히 많이 보정되며, 따라서 값이 작게 나오게 되는 효과가 있다. 그렇기 때문에 값이 범위가 대부분의 경우에 -2에서 2 사이에 위치하게 되는 경우가 많다. 밴드의 범위는 투자 대상이나 투자자들에 따라 다르게 사용되며 -2.5에서 2.5, 혹은 -3에서 3을 사용하는 경우도 있다. 이는 포지션이 열려 있는 시간, 혹은 포지션을 열지 않고 대기하는 시간과 크게 연결이 되는데 이 말은 투자 사이클에 크게 영향을 주기 때문에 이동평균 길이와 함께 투자 패턴을 고려하여 설정하는 게 중요하다.
이 지표는 다른 이동평균을 사용하는 지표들에 비교했을 때 최근 변동성을 고려하여 값을 보정한다는 게 가장 큰 특징이다. 이 때문에 좀 더 정량적이고 변동폭이 크던 적던 비교적 일정한 값 범위를 형성하며 정량적으로 분석할 수 있다는 장점이 있다.
마지막으로 PGO(Pretty Good Oscillator) 트레이딩 뷰 파인 스크립트 소스와 pandas-ta 소스를 공유하며 마친다.
- PGO(Pretty Good Oscillator) 트레이딩 뷰 파인 스크립트 지표 소스
//@version=5
indicator(title="Pretty Good Oscillator", shorttitle="PGO", format=format.price, precision=4, timeframe="", timeframe_gaps=true)
import TradingView/ta/7 as ta7
import blackcat1402/pandas_ta/7 as pta
ma(source, length, _type) =>
switch _type
"SMA" => ta.sma(source, length)
"EMA" => ta.ema(source, length)
"DEMA" => ta7.dema(source,length)
"TEMA" => ta7.tema(source,length)
"FRAMA" => ta7.frama(source,length)
"T3" => ta7.t3(source,length)
"TRIMA" => ta7.trima(source,length)
"RMA" => ta.rma(source, length)
"WMA" => ta.wma(source, length)
"HMA" => ta.hma(source, length)
"VWMA" => ta.vwma(source * volume, length)
"ALMA" => pta.alma(source, length)
"JMA" => pta.jma(source, length)
"SINWMA" => pta.sinwma(source, length)
"FWMA" => pta.fwma(source, length)
"LINREG" => pta.linreg(source, length)
"SWMA" => pta.swma(source)
"YIDYA" => pta.vidya(source, length)
"VWAP" => pta.vwap(source)
"ZLMA" => pta.zlma(source, length)
sourceInput = input(defval=close, title="Source")
sourceMALengthInput = input.int(defval=14, title="Source MA Length")
sourceMATypeInput = input.string(defval="SMA", title="MA Type", options = ["SMA", "EMA", "DEMA", "TEMA", "FRAMA", "T3", "TRIMA", "RMA", "WMA", "HMA", "VWMA", "ALMA", "JMA", "SINWMA", "FWMA", "LINREG", "SWMA", "VIDYA", "VWAP", "ZLMA"], display = display.data_window)
atrLengthInput = input.int(defval=14, title="ATR Length")
atrMALengthInput = input.int(defval=14, title="ATR MA Length")
atrMATypeInput = input.string(defval="EMA", title="MA Type", options = ["SMA", "EMA", "DEMA", "TEMA", "FRAMA", "T3", "TRIMA", "RMA", "WMA", "HMA", "VWMA", "ALMA", "JMA", "SINWMA", "FWMA", "LINREG", "SWMA", "VIDYA", "VWAP", "ZLMA"], display = display.data_window)
upperInput = input.float(2.0, title="Upper", group="Band Settings")
lowerInput = input.float(-2.0, title="Lower", group="Band Settings")
pgo = (sourceInput - ma(sourceInput, sourceMALengthInput, sourceMATypeInput)) / ma(ta.atr(atrLengthInput), atrMALengthInput, atrMATypeInput)
pgoPlot = plot(pgo, title="PGO", color=color.blue)
upperBand = hline(upperInput, "Upper Band", color=color.gray)
hline(0, "Middle Band", linestyle=hline.style_dotted, color=color.gray)
lowerBand = hline(lowerInput, "Lower Band", color=color.gray)
fill(upperBand, lowerBand, title="Background", color=color.new(color.gray, 90))
midLinePlot = plot(0, color = na, editable = false, display = display.none)
fill(pgoPlot, midLinePlot, 4, upperInput, top_color = color.green, bottom_color = color.new(color.green, 100), title = "Overbought Gradient Fill")
fill(pgoPlot, midLinePlot, lowerInput, -4, top_color = color.new(color.red, 100), bottom_color = color.red, title = "Oversold Gradient Fill")
- PGO(Pretty Good Oscillator) 트레이딩 뷰 파인 스크립트 전략 소스
//@version=5
strategy(title="Pretty Good Oscillator", shorttitle="PGO", margin_long=100, margin_short=100, default_qty_type=strategy.percent_of_equity, default_qty_value=50, commission_type=strategy.commission.percent, commission_value=0.2, pyramiding=0)
import TradingView/ta/7 as ta7
import blackcat1402/pandas_ta/7 as pta
ma(source, length, _type) =>
switch _type
"SMA" => ta.sma(source, length)
"EMA" => ta.ema(source, length)
"DEMA" => ta7.dema(source,length)
"TEMA" => ta7.tema(source,length)
"FRAMA" => ta7.frama(source,length)
"T3" => ta7.t3(source,length)
"TRIMA" => ta7.trima(source,length)
"RMA" => ta.rma(source, length)
"WMA" => ta.wma(source, length)
"HMA" => ta.hma(source, length)
"VWMA" => ta.vwma(source * volume, length)
"ALMA" => pta.alma(source, length)
"JMA" => pta.jma(source, length)
"SINWMA" => pta.sinwma(source, length)
"FWMA" => pta.fwma(source, length)
"LINREG" => pta.linreg(source, length)
"SWMA" => pta.swma(source)
"YIDYA" => pta.vidya(source, length)
"VWAP" => pta.vwap(source)
"ZLMA" => pta.zlma(source, length)
sourceInput = input(defval=close, title="Source")
sourceMALengthInput = input.int(defval=14, title="Source MA Length")
sourceMATypeInput = input.string(defval="SMA", title="MA Type", options = ["SMA", "EMA", "DEMA", "TEMA", "FRAMA", "T3", "TRIMA", "RMA", "WMA", "HMA", "VWMA", "ALMA", "JMA", "SINWMA", "FWMA", "LINREG", "SWMA", "VIDYA", "VWAP", "ZLMA"], display = display.data_window)
atrLengthInput = input.int(defval=14, title="ATR Length")
atrMALengthInput = input.int(defval=14, title="ATR MA Length")
atrMATypeInput = input.string(defval="EMA", title="MA Type", options = ["SMA", "EMA", "DEMA", "TEMA", "FRAMA", "T3", "TRIMA", "RMA", "WMA", "HMA", "VWMA", "ALMA", "JMA", "SINWMA", "FWMA", "LINREG", "SWMA", "VIDYA", "VWAP", "ZLMA"], display = display.data_window)
upperInput = input.float(2.0, title="Upper", group="Band Settings")
lowerInput = input.float(-2.0, title="Lower", group="Band Settings")
strategySignal = input.string("PGO-Band", title="Strategy Indicator", options = ["PGO-Band", "PGO-0 Cross"])
pgo = (sourceInput - ma(sourceInput, sourceMALengthInput, sourceMATypeInput)) / ma(ta.atr(atrLengthInput), atrMALengthInput, atrMATypeInput)
pgoPlot = plot(pgo, title="PGO", color=color.blue)
upperBand = hline(upperInput, "Upper Band", color=color.gray)
hline(0, "Middle Band", linestyle=hline.style_dotted, color=color.gray)
lowerBand = hline(lowerInput, "Lower Band", color=color.gray)
fill(upperBand, lowerBand, title="Background", color=color.new(color.gray, 90))
midLinePlot = plot(0, color = na, editable = false, display = display.none)
fill(pgoPlot, midLinePlot, 4, upperInput, top_color = color.green, bottom_color = color.new(color.green, 100), title = "Overbought Gradient Fill")
fill(pgoPlot, midLinePlot, lowerInput, -4, top_color = color.new(color.red, 100), bottom_color = color.red, title = "Oversold Gradient Fill")
startDate = input.time(defval=timestamp("01 Jan 1970 00:00 +0000"), group = "Test Range")
finishDate = input.time(defval=timestamp("31 Dec 2025 24:00 +0000"), group = "Test Range")
time_condition = time >= startDate and time <= finishDate
if(time_condition)
if (strategySignal == "PGO-Band")
if ta.crossover(pgo, lowerInput)
strategy.entry("매수", strategy.long, oca_type=strategy.oca.cancel, comment="매수")
if ta.crossunder(pgo, upperInput)
strategy.close_all('매도')
if (strategySignal == "PGO-0 Cross")
if ta.crossover(pgo, 0)
strategy.entry("매수", strategy.long, oca_type=strategy.oca.cancel, comment="매수")
if ta.crossunder(pgo, 0)
strategy.close_all('매도')
bgcolor(strategy.position_size > 0 ? color.new(color.yellow,90) : na)
- PGO(Pretty Good Oscillator) pandas-ta 소스
import pandas as pd
import pandas_ta as ta
import FinanceDataReader as fdr
data = fdr.DataReader('005930')
pgo = ta.pgo(high=data['High'], low=data['Low'], close=data['Close'], length=14)
data = pd.concat([data, pgo], axis=1)
data.dropna(inplace=True)