Fractionally Differentiated Features - jaeaehkim/trading_system_beta GitHub Wiki

Motivation

  • Financial Series(์ผ๋ฐ˜์ ์ธ ์‹œ๊ณ„์—ด๋กœ ๋‚˜ํƒ€๋‚˜๋Š” ๊ธˆ์œต ๋ฐ์ดํ„ฐ. ex ๊ฐ€๊ฒฉ๋ฐ์ดํ„ฐ)๋Š” Arbitrage์— ์˜ํ•ด ๊ธฐ๋ณธ์ ์ธ ์•ŒํŒŒ๊ฐ€ ์†Œ๋ฉธ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์ „์ฒ˜๋ฆฌ ๋˜์ง€ ์•Š์€ ์ƒํƒœ์—์„  Low Signal/Noise๋ฅผ ๋ณด์ธ๋‹ค๊ณ  ์•Œ๋ ค์ ธ ์žˆ๋‹ค. Feature๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์šฐ๋ฆฌ๋Š” ๊ฐ์ข… ๋‹ค์–‘ํ•œ ์—ฐ์‚ฐ์ž๋ฅผ ํ†ตํ•ด์„œ ์•ŒํŒŒ๊ฐ€ ๋‚จ์•„์žˆ๋Š” Series๋กœ ๋ณ€ํ™˜์‹œ์ผœ์•ผ ํ•œ๋‹ค.
  • ์ผ๋‹จ, ํ†ต๊ณ„ํ•™ ์ชฝ์—์„œ Regression Analysis๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด Series๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜์—ฌ Factor(Feature)๋ฅผ ๊ณ„์‚ฐํ•˜๊ธฐ ๋ณด๋‹ค๋Š” Differentiation์„ ํ•˜๊ณ  Fitting ์ž‘์—…์„ ์‹œ์ž‘ํ•œ๋‹ค. ์ด๋•Œ, Diff๋Š” ๋‹จ์ˆœ 1,2,3์ฐจ์˜ ์ •์ˆ˜ ์ฐจ๋ถ„์œผ๋กœ ํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๊ณ , 1์ฐจ ์ฐจ๋ถ„๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ 99%์ด๋‹ค.
  • ๊ฐ€๊ฒฉ ์‹œ๊ณ„์—ด์˜ ๊ฒฝ์šฐ ๋ชจ๋“  ๊ฐ’์ด ๊ฐ€๊ฒฉ ์ˆ˜์ค€(Level)์— ๋Œ€ํ•œ ๊ธฐ์–ต์ด ์กด์žฌํ•˜๊ณ  ์ด๋ฅผ ์ •์ˆ˜ ์ฐจ๋ถ„ํ•˜๊ฒŒ ๋˜๋ฉด ์ •์ƒ์„ฑ์€ ํ™•๋ณด๋˜์ง€๋งŒ ๊ฑฐ์˜ ๋ชจ๋“  ๊ธฐ์–ต์ด ์‚ญ์ œ๋˜๋ฏ€๋กœ (์ •ํ™•ํžˆ๋Š” Return์ •๋„์˜ ๊ธฐ์–ต์œผ๋กœ๋Š” ์ด๋ฏธ ๋งŽ์€ ์•ŒํŒŒ๊ฐ€ ์†Œ์ง„๋˜๊ณ  ์žˆ์–ด์„œ) ์ž”์—ฌ Signal์„ ๋ฝ‘์•„๋‚ด๊ธฐ ์œ„ํ•ด์„  ๋ณต์žกํ•œ ์—ฐ์‚ฐ์ž๋ฅผ ํ™œ์šฉํ•˜๊ฒŒ ๋˜๊ณ  ์ด๋Š” ๊ณผ์ตœ์ ํ™”์˜ ์›์ธ์„ ์ œ๊ณตํ•˜๊ฒŒ ๋œ๋‹ค.
  • ์œ„์˜ ๋ฌธ์ œ๋Š” ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์ ‘๊ทผํ•ด๋„ ๋‚จ์•„์žˆ๋Š” ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ์ด๋‚˜ ๋””๋ฒจ๋กญ ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ์—ฌ์ง€๊ฐ€ ์žˆ๋Š” ๋ถ€๋ถ„์€ '์ •์ˆ˜ ์ฐจ๋ถ„'์ด ์•„๋‹Œ '์‹ค์ˆ˜ ์ฐจ๋ถ„'์„ ํ†ตํ•ด์„œ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ตœ๋Œ€ํ•œ ์‚ด๋ ค๋‚ด๋Š” ์ƒˆ๋กœ์šด Series๋ฅผ ๋งŒ๋“ค์–ด ๋‚ด๊ณ  ์ด๋ฅผ ํ†ตํ•ด Feature๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฒƒ์—์„œ ์ž”์—ฌ ์‹ ํ˜ธ๋ฅผ ์กฐ๊ธˆ์ด๋ผ๋„ ๋Š˜๋ฆด ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์€ ๋™๊ธฐ๊ฐ€ ๋œ๋‹ค.

Dilemma : Stationarity vs Memory

  • Memory(๊ธฐ์–ต) : Series์˜ ํ‰๊ท ์„ ์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ณ€๋™์‹œํ‚ค๋Š” ์ด์ „ Level์˜ ์ผ์ • ๊ธฐ๊ฐ„์˜ ์ •๋ณด์— ๋Œ€ํ•œ ์กด์žฌ ์œ ๋ฌด. ๋‹ค ์—†์–ด์ง„ ์ƒํƒœ๋ฅผ Stationarity 100%๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ์Œ.
    • Weak Stationaryํ•˜๋ฉด์„œ ์ž”์—ฌ Signal์ด ๋งŽ์€ Series๋กœ ๋ณ€ํ™˜ํ•˜๋Š”๊ฒŒ ์ค‘์š”
  • ์ง€๋„ํ•™์Šต ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ๋Œ€๊ฐœ Stationarity๊ฐ€ ํ•„์š”ํ•จ. ์ด์œ ๋Š” ๋ ˆ์ด๋ธ” ๋˜์ง€ ์•Š์€ ์ƒˆ๋กœ์šด ๊ด€์ธก ๊ฐ’(Test or Real Data)์„ ๋ ˆ์ด๋ธ” ๋œ ์˜ˆ์ œ(Train Data)์˜ ์ง‘ํ•ฉ์œผ๋กœ mappingํ•  ํ•„์š”์„ฑ์ด ์žˆ๊ณ  ์ด๋กœ๋ถ€ํ„ฐ ์ƒˆ๋กœ์šด ๊ด€์ธก ๊ฐ’์˜ ๋ ˆ์ด๋ธ”์„ ์ถ”๋ก ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ฆ‰, Stationarity๊ฐ€ ๋„ˆ๋ฌด ์—†๋‹ค๋ฉด ๋ฐœ์‚ฐ์˜ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์Œ.
  • Stationarity๋Š” ์˜ˆ์ธก๋ ฅ์„ ๋‹ด๋ณดํ•˜์ง„ ๋ชปํ•˜๊ณ  ๊ณ ์„ฑ๋Šฅ ML ๋ชจ๋ธ์„ ์œ„ํ•œ ํ•„์š”,๋ถˆ์ถฉ๋ถ„ ์กฐ๊ฑด
    • Literature Review : (Hosking,1981) > Johansen > Nielsen > MacKinnon > Jensen > Jones > Popiel > Cavaliere > Taylor
      • Hosking๋ถ€ํ„ฐ ์ด๋ฏธ ARMIA Process Family๋ฅผ ์‹ค์ˆ˜ ์ฐจ๋ถ„์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ์ผ๋ฐ˜ํ™”ํ•˜์˜€๊ณ  ์ดํ›„์—” Continuous Probablistic Process์˜ ์‹ค์ˆ˜ ์ฐจ๋ถ„์˜ ์—ฐ์‚ฐ ์ตœ์ ํ™”์™€ ๊ด€๋ จ

The Method

Backshift Operator

  • image
  • image | image
  • image
  • image
    • Backshift Operator๋ฅผ ํ†ตํ•ด ๋ณต์žกํ•˜๊ฒŒ ๊ผฌ๋ฆฌ๋ฅผ ๋ฌด๋Š” ์‹œ๊ณ„์—ด ๊ณ„์‚ฐ์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๊ณ  ์ด ๊ณผ์ •์—์„œ ์ดํ•ญ ๊ณ„์‚ฐ์ด ์‚ฌ์šฉ๋จ.

Long Memory & Iterative Calculation for Weight

  • image
  • image
  • image
    • image image
  • image
    • Backshift๋ฅผ ํ†ตํ•ด์„œ X_t๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๊ณ  weights๋ฅผ ๋‹จ์ˆœ ๋‚˜์—ดํ•œ ๊ฒƒ๊ณผ Iterativeํ•˜๊ฒŒ ํ‘œํ˜„ํ•œ ์ˆ˜์‹์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์•„๋ž˜ ์ฝ”๋“œ๋Š” weights๋ฅผ iterativeํ•˜๊ฒŒ ๊ณ„์‚ฐํ•˜๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋ƒ„
def getWeights(d, size):
    # thresh > 0 drops insignificant weights
    w = [1.0]
    for k in range(1, size):
        w_ = -w[-1] / k * (d - k + 1)
        w.append(w_)
    w = np.array(w[::-1]).reshape(-1, 1)
    return w

Implementation

  • ์œ„์—์„  '์‹ค์ˆ˜ ์ฐจ๋ถ„'์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ํ•„์š”ํ•œ ์‚ฌ์ „ ์ง€์‹์„ ๋‹ค๋ค˜๊ณ  ์‹ค์ œ๋กœ ๊ตฌํ˜„ํ•˜๋Š” 2๊ฐ€์ง€ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์†Œ๊ฐœ

Expanding Window

  • image
  • image
  • image
  • image image

    • ์‹ค์ œ ์‹œ๊ณ„์—ด์€ ์œ ํ•œํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ด€์ธก๊ฐ’์ด T๊ฐœ๊นŒ์ง€ ์ œํ•œ๋œ๋‹ค. ๊ฐ€์ค‘๊ฐ’์€ w_k๋Š” 1~T-1๊นŒ์ง€ 0์ด ์•„๋‹Œ ๊ฐ’์„ ๊ฐ–๋Š”๋‹ค. ์ด๋กœ ์ธํ•ด, ์ดˆ๊ธฐ ํฌ์ธํŠธ์™€ ์ตœ์ข… ํฌ์ธํŠธ๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ๊ธฐ์–ต๋Ÿ‰์„ ๊ฐ–๊ฒŒ ๋œ๋‹ค. Expanding Window ๊ธฐ๋ฒ•์—์„  ์„œ๋กœ ๋‹ค๋ฅธ ๊ธฐ์–ต๋Ÿ‰์„ ๊ด€์—ฌํ•˜์ง€ ์•Š๊ณ  ๋‹จ์ˆœํžˆ ์œ„์˜ X_t, w๋ฅผ T๊ฐœ๊นŒ์ง€์˜ ๊ฒฝ์šฐ๋กœ ์ œํ•œํ•˜์—ฌ ๊ณ„์‚ฐํ•œ๋‹ค.
    • ๊ฐ ํฌ์ธํŠธ ๋งˆ๋‹ค์˜ ๊ธฐ์–ต๋Ÿ‰์„ relative weight-loss๋ฅผ ํ†ตํ•ด ์ •๋Ÿ‰ํ™” ํ•  ์ˆ˜ ์žˆ๋‹ค. Fixed-Width Window Fracdiff์—์„œ๋Š” ์ด๋ฅผ ํ™œ์šฉํ•œ ์‹ค์ˆ˜ ์ฐจ๋ถ„์„ ์‚ฌ์šฉํ•œ๋‹ค.
def fracDiff(series, d, thres=0.01):
    '''
    Increasing width window, with treatment of NaNs
    Note: For thres=1, nothing is skipped.
    Note 2: d can be any positive fractional, not necessarily bounded [0,1]
    '''
    # 1) Compute weights for the longest series
    w = getWeights(d, series.shape[0])
    # 2) Determine initial calcs to be skipped based on the weight-loss threshold
    w_ = np.cumsum(abs(w))
    w_ /= w_[-1]
    skip = w_[w_ > thres].shape[0]
    # 3) Apply weights to values
    df = {}
    for name in series.columns:
        seriesF, df_ = series[name](/jaeaehkim/trading_system_beta/wiki/name).fillna(method='ffill').dropna(), pd.Series(index=series.index) # bug in the original code
        for iloc in range(skip, seriesF.shape[0]):
            loc = seriesF.index[iloc]
            if not np.isfinite(series.loc[loc, name]):
                continue # exclude NAs
            a = w[-(iloc + 1):, :].T
            b = seriesF.loc[:loc]
            df_[loc] = np.dot(a, b)[0, 0]
        df[name] = df_.copy(deep=True)
    df = pd.concat(df, axis=1)
    return df

Fixed-Width Window Fracdiff

  • image
  • image
  • image
  • image
  • image image
    • lambda๋ฅผ ์ด์šฉํ•ด์„œ threshold ์ผ์ • ์ˆ˜์ค€ ์ดํ•˜๋Š” ๊ฐ€์ค‘๊ฐ’์„ ์‚ญ์ œํ•˜์—ฌ ๊ฐ€์ค‘๊ฐ’์˜ ๋™์ผํ•œ ๋ฒกํ„ฐ(๊ธธ์ด๊ฐ€ l*๋กœ ์ผ์ •)๋ฅผ Xt์˜ ๋ชจ๋“  ๊ณ„์‚ฐ์— ์‚ฌ์šฉํ•˜์—ฌ ์ผ๊ด€์„ฑ์„ ๋ถ€์—ฌํ•จ.
def getWeights_FFD(d, thres):
    w, k = [1.0], 1
    while True:
        w_ = -w[-1] / k * (d - k + 1)
        if abs(w_) < thres:
            break
        w.append(w_)
        k += 1
    return np.array(w[::-1]).reshape(-1, 1)

def fracDiff_FFD(series, d, thres=1e-5):
    # Constant with window (new solution)
    w =  getWeights_FFD(d, thres)
    width, df = len(w) - 1, {}
    
    for name in series.columns:
        seriesF, df_ = series[name](/jaeaehkim/trading_system_beta/wiki/name).fillna(method='ffill').dropna(), pd.Series(index=series.index)
        for iloc1 in range(width, seriesF.shape[0]):
            loc0, loc1 = seriesF.index[iloc1 - width], seriesF.index[iloc1]
            if not np.isfinite(series.loc[loc1, name]):
                continue # exclude NAs
            df_[loc1] = np.dot(w.T, seriesF.loc[loc0:loc1])[0, 0]
        df[name] = df_.copy(deep=True)
    df = pd.concat(df, axis=1)
    return df

Stationarity with Maximum Memory Preservation

  • adfuller test๋ฅผ ํ™œ์šฉํ•ด์„œ d ๊ฐ’์„ 0 ~ 1๊นŒ์ง€ 0.01 ~ 0.1 ๋‹จ์œ„๋กœ ํ…Œ์ŠคํŠธ ํ•˜์—ฌ ์ตœ์  ์ง€์ ์„ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
  • ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ d=0.5~0.6 ์ •๋„๋กœ ์‚ฌ์šฉํ•˜๋Š”๋“ฏ.

Application to Quant System

  • ๊ธฐ์กด์— feature ๋งŒ๋“ค ๋•Œ๋Š” Event bar ๋ฐ์ดํ„ฐ์— ์—ฐ์‚ฐ์ž๋ฅผ ๋”ํ•ด ๊ณ„์‚ฐํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•จ
  • ๊ฐ€๊ฒฉ ์‹œ๊ณ„์—ด, Volume Bar, Event Bar ๊ฐ๊ฐ์— ์‹ค์ˆ˜ ์ฐจ๋ถ„์„ ์ ์šฉํ•˜์—ฌ feature๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค๋ฉด ๋‹ค๋ฅธ ์ •๋ณด๋ฅผ ๋ฝ‘์•„๋‚ผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์œผ๋กœ ๋ณด์ž„