피셔 트랜스폼(Fisher Transform)은 CTI(Correlation Trend Indicator), 상대 활력 지수(RVI, Relative Vigor Index)에서 소개했던 존 엘러스(John Ehlers)에 의해 만들어진 지표로 통계학에 등장하는 그 피셔 트랜스폼이 맞다. 피셔 트랜스폼은 관찰 기간 내의 가격을 가지고 정규 분포화 하고 현재 가격의 위치를 정규분포상의 위치로 표현하는 지표다. 정규화를 하기 때문에 관찰기간 내의 가격 변동이 심할 경우, -2와 2 사이의 밴드를 이탈하게 되고 이를 기준으로 과매도, 과매수를 판단하는 지표라고 이해하면 된다.
피셔 트랜스폼은 관찰 기간 내 가격을 기준으로 정규화하기 때문에 관찰 기간 내내 상방이던 하방이던일정한 방향으로 크게 움직였을 경우, 그 변동폭이 반영되어 이후 발생하는 변동이 그보다 줄어들면 작은 값을 보이게 된다. 관찰기간 내 평균 가격일 경우 피셔 트랜스폼의 값은 0인 거고 관찰기간 내 가격 상하단을 넘어가면 갈수록 -던 +던 큰 값을 보이게 되는 구조며, 관찰기간 변동성은 상하단의 범위를 늘리는데 영향을 준다.
정규 분포라 하면 -2와 2 사이에 들어와야 하는 값이 95%, -3과 3 사이에 들어와야 하는 값이 99.7% 정도 될 텐데, 보통 피셔 트랜스폼은 기간을 짧게 9~10일 정도로 놓고 사용하는 게 일반적이다 보니 4를 넘어가는 값을 보는 것도 어렵지 않으며, 관찰 기간을 수백일 단위로 늘린다 하더라도 주가가 보통 장기적으로 우상향 하고 피셔 트랜스폼 0은 관찰 기간 동안의 평균값을 잡고 있으니 가격이 상승할 때 크게 튀어 오르는 모습을 자주 볼 수 있다.
매매 방법은 밴드를 정해서 하단 밴드 밑에서는 과매도 구간, 상단 밴드 위에서는 과매수 구간으로 보며 하단 밴드를 상향 돌파할 경우 매수하고, 상단 밴드를 하단 돌파할 경우 매도하는 전략이다. 피셔 트랜스폼은 주가에 대한 정규 분포를 이용하기 때문에 단기간에 변동성이 크게 늘어날 경우, 값을 포착하기에 굉장히 용이하기 때문에 해당 전략으로 단기 과매수, 과매도를 판단하기에 아주 적절하다. 통상 기준이 되는 상단 하단 밴드는 -1.5 ~ 1.5를 사용하거나 -2~2를 사용하는 경우가 많다.
다른 매매 방법으로는 0을 기준으로 0보다 크면 매수, 0보다 작으면 매도하는 전략인데, 이 전략은 피셔 트랜스폼은 정규 분포를 사용하며 기준 값인 0은 관찰기간 내의 평균값이라고 이해하면 되기 때문에 관찰기간 내 평균보다 크면 매수하는 방식이라고 보면 된다. 이 말은 같은 기간의 단순 이동 평균 위에 있으면 매수, 아래에 있으면 매도를 하는 것과 완전히 같은 결과가 나온다. 굳이 피셔 트랜스폼을 이용하여 적용할 전략은 아니며, 다른 전략과 결합하여 조건화할 수는 있겠다.
피셔 트랜스폼의 또 다른 특징은 피셔 트랜스폼 라인 외에 이를 한 칸 뒤로 미룬 트리거(Trigger) 라인이라는 걸 같이 그려준다는 건데, 이를 이용하여 매매에 임할 수 있다. 앞선 전략에 트리거 라인을 이용하거나, 위 전략들과 결합하여 트리거 라인과 크로스 여부를 체크하여 판단하는 방식이다. 그 외에는 피셔 트랜스폼과 트리거 라인의 크로스 시점에 매수, 매도를 하는 방법이 있는데 이는 허위 시그널이 많이 나와 트리거 라인을 적당히 더 뒤로 미뤄서 적용하는 방법을 검토해봐야 한다.
마지막으로 피셔 트랜스폼(Fisher Transform) 트레이딩 뷰 파인 스크립트 소스와 pandas-ta 소스를 공유하며 마친다.
- 피셔 트랜스폼(Fisher Transform) 트레이딩 뷰 파인 스크립트 지표 소스
//@version=5
indicator(title="Fisher Transform", shorttitle="Fisher", format=format.price, precision=2, timeframe="", timeframe_gaps=true)
lengthInput = input.int(9, minval=1, title="Length")
triggerLaggingInput = input.int(1, title="Trigger Lagging")
upperInput1 = input.float(1.0, title="Upper", group="1st Band Settings")
lowerInput1 = input.float(-1.0, title="Lower", group="1st Band Settings")
upperInput2 = input.float(2.0, title="Upper", group="2nd Band Settings")
lowerInput2 = input.float(-2.0, title="Lower", group="2nd Band Settings")
high_ = ta.highest(hl2, lengthInput)
low_ = ta.lowest(hl2, lengthInput)
round_(val) => val > .99 ? .999 : val < -.99 ? -.999 : val
value = 0.0
value := round_(.66 * ((hl2 - low_) / (high_ - low_) - .5) + .67 * nz(value[1]))
fisher = 0.0
fisher := .5 * math.log((1 + value) / (1 - value)) + .5 * nz(fisher[1])
trigger = fisher[1]
fisherplot = plot(fisher, color=color.orange, title="Fisher")
triggerplot = plot(trigger, color=color.purple, title="Trigger")
midLinePlot = plot(0, color = na, editable = false, display = display.none)
fill(fisherplot, midLinePlot, 3, upperInput1, top_color = color.green, bottom_color = color.new(color.green, 100), title = "Overbought Gradient Fill")
fill(fisherplot, midLinePlot, lowerInput1, -3, top_color = color.new(color.red, 100), bottom_color = color.red, title = "Oversold Gradient Fill")
upperBand1 = hline(upperInput1, "1st Upper Band", color=color.gray)
hline(0, "Middle Band", linestyle=hline.style_dotted, color=color.gray)
lowerBand1 = hline(lowerInput1, "1st Lower Band", color=color.gray)
fill(upperBand1, lowerBand1, title="Background", color=color.new(color.gray, 90))
upperBand2 = hline(upperInput2, "2nd Upper Band", color=color.gray)
lowerBand2 = hline(lowerInput2, "2nd Lower Band", color=color.gray)
fill(upperBand2, lowerBand2, title="Background2", color=color.new(color.gray, 90))
- 피셔 트랜스폼(Fisher Transform) 트레이딩 뷰 파인 스크립트 전략 소스
//@version=5
strategy(title="Fisher Transform", shorttitle="Fisher", 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)
lengthInput = input.int(9, minval=1, title="Length")
triggerLaggingInput = input.int(1, title="Trigger Lagging")
upperInput1 = input.float(1.0, title="Upper", group="1st Band Settings")
lowerInput1 = input.float(-1.0, title="Lower", group="1st Band Settings")
upperInput2 = input.float(2.0, title="Upper", group="2nd Band Settings")
lowerInput2 = input.float(-2.0, title="Lower", group="2nd Band Settings")
high_ = ta.highest(hl2, lengthInput)
low_ = ta.lowest(hl2, lengthInput)
round_(val) => val > .99 ? .999 : val < -.99 ? -.999 : val
value = 0.0
value := round_(.66 * ((hl2 - low_) / (high_ - low_) - .5) + .67 * nz(value[1]))
fisher = 0.0
fisher := .5 * math.log((1 + value) / (1 - value)) + .5 * nz(fisher[1])
trigger = fisher[1]
fisherplot = plot(fisher, color=color.orange, title="Fisher")
triggerplot = plot(trigger, color=color.purple, title="Trigger")
midLinePlot = plot(0, color = na, editable = false, display = display.none)
fill(fisherplot, midLinePlot, 3, upperInput1, top_color = color.green, bottom_color = color.new(color.green, 100), title = "Overbought Gradient Fill")
fill(fisherplot, midLinePlot, lowerInput1, -3, top_color = color.new(color.red, 100), bottom_color = color.red, title = "Oversold Gradient Fill")
upperBand1 = hline(upperInput1, "1st Upper Band", color=color.gray)
hline(0, "Middle Band", linestyle=hline.style_dotted, color=color.gray)
lowerBand1 = hline(lowerInput1, "1st Lower Band", color=color.gray)
fill(upperBand1, lowerBand1, title="Background", color=color.new(color.gray, 90))
upperBand2 = hline(upperInput2, "2nd Upper Band", color=color.gray)
lowerBand2 = hline(lowerInput2, "2nd Lower Band", color=color.gray)
fill(upperBand2, lowerBand2, title="Background2", color=color.new(color.gray, 90))
strategySignal = input.string("Fisher-Band2", title="Strategy Indicator", options = ["Fisher-Band1", "Fisher-Band2", "Fisher-0 Cross", "Fisher-Trigger Cross"])
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 == "Fisher-Band1")
if ta.crossover(fisher, lowerInput1)
strategy.entry("매수", strategy.long, oca_type=strategy.oca.cancel, comment="매수")
if ta.crossunder(fisher, upperInput1)
strategy.close_all('매도')
if (strategySignal == "Fisher-Band2")
if ta.crossover(fisher, lowerInput2)
strategy.entry("매수", strategy.long, oca_type=strategy.oca.cancel, comment="매수")
if ta.crossunder(fisher, upperInput2)
strategy.close_all('매도')
if (strategySignal == "Fisher-0 Cross")
if ta.crossover(fisher, 0)
strategy.entry("매수", strategy.long, oca_type=strategy.oca.cancel, comment="매수")
if ta.crossunder(fisher, 0)
strategy.close_all('매도')
if (strategySignal == "Fisher-Trigger Cross")
if ta.crossover(fisher, trigger)
strategy.entry("매수", strategy.long, oca_type=strategy.oca.cancel, comment="매수")
if ta.crossunder(fisher, trigger)
strategy.close_all('매도')
bgcolor(strategy.position_size > 0 ? color.new(color.yellow,90) : na)
- 피셔 트랜스폼(Fisher Transform) pandas-ta 소스
import pandas as pd
import pandas_ta as ta
import FinanceDataReader as fdr
data = fdr.DataReader('005930')
fisher = ta.fisher(high=data['High'], low=data['Low'], length=9, signal=1)
data = pd.concat([data, fisher], axis=1)
data.dropna(inplace=True)