คำนำ
ต่อจากหนังสือเล่มที่แล้ว เราได้ตีพิมพ์บทความสองบทความในชุดบทความเกี่ยวกับ การสร้างพอร์ตโฟลิโอสินทรัพย์ดิจิทัลที่มีประสิทธิภาพโดยใช้แบบจำลองหลายปัจจัย:“พื้นฐานทางทฤษฎี”、“การประมวลผลข้อมูลล่วงหน้า”
นี่คือบทความที่สาม: การทดสอบความถูกต้องของปัจจัย
หลังจากกำหนดค่าปัจจัยเฉพาะแล้ว จำเป็นต้องดำเนินการทดสอบความถูกต้องของปัจจัยและปัจจัยคัดกรองที่ตรงตามข้อกำหนดด้านนัยสำคัญ ความเสถียร ความน่าเบื่อ และอัตราผลตอบแทนก่อน การทดสอบความถูกต้องของปัจจัยทำได้โดยการวิเคราะห์ความสัมพันธ์ระหว่างปัจจัย มูลค่าของงวดปัจจุบันและความสัมพันธ์ของอัตราผลตอบแทนที่คาดหวังเพื่อกำหนดความถูกต้องของปัจจัย โดยทั่วไปมี 3 วิธีแบบคลาสสิก:
- วิธี IC/IR: ค่า IC/IR คือค่าสัมประสิทธิ์สหสัมพันธ์ระหว่างค่าตัวประกอบและผลตอบแทนที่คาดหวัง ยิ่งค่าปัจจัยมาก ประสิทธิภาพก็จะยิ่งดีขึ้น 
- ค่า T (วิธีการถดถอย): ค่า T สะท้อนถึงความสำคัญของสัมประสิทธิ์หลังจากการถดถอยเชิงเส้นของผลตอบแทนของงวดถัดไปจากค่าตัวประกอบของงวดปัจจุบัน โดยการเปรียบเทียบว่าค่าสัมประสิทธิ์การถดถอยผ่านการทดสอบ t หรือไม่ เราสามารถตัดสินการมีส่วนร่วมของ ค่าตัวประกอบของงวดปัจจุบันเทียบกับผลตอบแทนของงวดถัดไป โดยทั่วไปจะใช้ในแบบจำลองการถดถอยหลายตัวแปร (เช่น หลายปัจจัย) 
- วิธีการทดสอบย้อนกลับแบบลำดับชั้น: วิธีการทดสอบย้อนกลับแบบลำดับชั้นจะแบ่งโทเค็นตามค่าปัจจัย จากนั้นคำนวณอัตราผลตอบแทนของโทเค็นแต่ละชั้นเพื่อกำหนดความซ้ำซ้อนของปัจจัยต่างๆ 
1. กฎหมาย IC/IR
(1) คำจำกัดความของ IC/IR
IC: ค่าสัมประสิทธิ์ข้อมูล ซึ่งแสดงถึงความสามารถของปัจจัยในการทำนายผลตอบแทนของโทเค็น ค่า IC ของช่วงเวลาหนึ่งคือค่าสัมประสิทธิ์สหสัมพันธ์ระหว่างค่าตัวประกอบของช่วงเวลาปัจจุบันและอัตราผลตอบแทนของช่วงเวลาถัดไป


ยิ่ง IC ใกล้ 1 มากเท่าใด ความสัมพันธ์เชิงบวกระหว่างค่าปัจจัยและอัตราผลตอบแทนของงวดถัดไปก็จะยิ่งแข็งแกร่งขึ้นเท่านั้น IC= 1 หมายความว่าการเลือกสกุลเงินของปัจจัยนั้นแม่นยำ 100% ซึ่งสอดคล้องกับโทเค็นที่มีคะแนนอันดับสูงสุด โทเค็นที่เลือกจะถูกใช้ในรอบการปรับตำแหน่งถัดไป , เพิ่มขึ้นมากที่สุด;
ยิ่ง IC ใกล้ -1 มากเท่าใด ความสัมพันธ์เชิงลบระหว่างค่าปัจจัยและอัตราผลตอบแทนในช่วงถัดไปก็จะยิ่งแข็งแกร่งขึ้นเท่านั้น หาก IC=-1 หมายความว่าโทเค็นที่มีอันดับสูงสุดจะมีการลดลงมากที่สุดในช่วงถัดไป วงจรการปรับสมดุลซึ่งเป็นการผกผันที่สมบูรณ์ ดัชนี;
ถ้า IC ใกล้ 0 มากขึ้น หมายความว่าความสามารถในการคาดการณ์ของปัจจัยนั้นอ่อนมาก ซึ่งบ่งชี้ว่าปัจจัยนั้นไม่มีความสามารถในการคาดการณ์สำหรับโทเค็น
IR: อัตราส่วนข้อมูลซึ่งแสดงถึงความสามารถของปัจจัยในการรับอัลฟ่าที่เสถียร IR คือค่าเฉลี่ย IC ของทุกงวดหารด้วยค่าเบี่ยงเบนมาตรฐานของ IC ของทุกงวด

เมื่อค่าสัมบูรณ์ของ IC มากกว่า 0.05 (0.02) ความสามารถในการเลือกหุ้นของปัจจัยจะแข็งแกร่ง เมื่อ IR มากกว่า 0.5 ปัจจัยนี้มีความสามารถที่แข็งแกร่งในการได้รับผลตอบแทนส่วนเกินอย่างคงที่
(2) วิธีคำนวณไอซี
- IC ปกติ (สหสัมพันธ์เพียร์สัน): คำนวณค่าสัมประสิทธิ์สหสัมพันธ์เพียร์สัน ซึ่งเป็นค่าสัมประสิทธิ์สหสัมพันธ์แบบคลาสสิกที่สุด อย่างไรก็ตาม วิธีการคำนวณนี้มีข้อสันนิษฐานหลายประการ เช่น ข้อมูลมีความต่อเนื่อง มีการกระจายแบบปกติ ตัวแปรทั้งสองเป็นไปตามความสัมพันธ์เชิงเส้น เป็นต้น 

- อันดับ IC (สัมประสิทธิ์สหสัมพันธ์อันดับของสเปียร์แมน): คำนวณค่าสัมประสิทธิ์สหสัมพันธ์อันดับของสเปียร์แมน อันดับแรกเรียงลำดับตัวแปรทั้งสอง จากนั้นคำนวณค่าสัมประสิทธิ์สหสัมพันธ์ของเพียร์สันตามผลลัพธ์ที่เรียงลำดับค่าสัมประสิทธิ์สหสัมพันธ์อันดับของสเปียร์แมนจะประเมินความสัมพันธ์แบบโมโนโทนิกระหว่างตัวแปรสองตัว และได้รับผลกระทบจากค่าผิดปกติของข้อมูลน้อยกว่าเนื่องจากจะถูกแปลงเป็นค่าที่เรียงลำดับค่าสัมประสิทธิ์สหสัมพันธ์แบบเพียร์สันจะประเมินความสัมพันธ์เชิงเส้นระหว่างตัวแปรสองตัว ซึ่งไม่เพียงแต่มีข้อกำหนดเบื้องต้นบางประการสำหรับข้อมูลต้นฉบับเท่านั้น แต่ยังได้รับผลกระทบอย่างมากจากค่าผิดปกติของข้อมูลอีกด้วย ในการคำนวณในชีวิตจริง การค้นหาอันดับ IC จะมีความสม่ำเสมอมากกว่า 
(3) การใช้รหัสวิธี IC/IR
สร้างรายการค่าวันที่และเวลาที่ไม่ซ้ำกันโดยเรียงลำดับวันที่และเวลาจากน้อยไปหามาก - บันทึกวันที่ปรับสมดุลใหม่ def choosedate(dateList, รอบ)
class TestAlpha(object):
    def __init__(self, ini_data):
        self.ini_data = ini_data    
    def chooseDate(self, cycle, start_date, end_date):
        '''
        cycle: day, month, quarter, year
df: กรอบข้อมูลดั้งเดิม df, การประมวลผลคอลัมน์วันที่
        '''
        chooseDate = []
        dateList = sorted(self.ini_data[self.ini_data['date'].between(start_date, end_date)]['date'].drop_duplicates().values) 
        dateList = pd.to_datetime(dateList) 
        for i in range(len(dateList)-1):
            if getattr(dateList[i], cycle) != getattr(dateList[i + 1 ], cycle):
                    chooseDate.append(dateList[i])
            
        chooseDate.append(dateList[-1 ])
        chooseDate = [date.strftime('%Y-%m-%d') for date in chooseDate]
        return chooseDate      
      def ICIR(self, chooseDate, factor):
# 1. ขั้นแรกให้แสดง IC ของวันที่ปรับตำแหน่งแต่ละตำแหน่ง นั่นคือ ICT
        testIC = pd.DataFrame(index=chooseDate, columns=['normalIC','rankIC'])
        dfFactor = self.ini_data[self.ini_data['date'].isin(chooseDate)][['date','name','price', factor]]
        for i in range(len(chooseDate)-1):
            # ( 1) normalIC
            X = dfFactor[dfFactor['date'] == chooseDate[i]][['date','name','price', factor]].rename(columns={'price':'close 0'})
            Y = pd.merge(X, dfFactor[dfFactor['date'] == chooseDate[i+ 1 ]][['date','name','price']], on=['name']).rename(columns={'price':'close 1'}) 
            Y['returnM'] = (Y['close 1'] - Y['close 0']) / Y['close 0']
            Yt = np.array(Y['returnM'])
            Xt = np.array(Y[factor])
            Y_mean = Y['returnM'].mean()
            X_mean = Y[factor].mean()
            num = np.sum((Xt-X_mean)*(Yt-Y_mean))
            den = np.sqrt(np.sum((Xt-X_mean)** 2)*np.sum((Yt-Y_mean)** 2))
            normalIC = num / den # pearson correlation
            # ( 2) rankIC
            Yr = Y['returnM'].rank()
            Xr = Y[factor].rank()
            rankIC = Yr.corr(Xr)
            testIC.iloc[i] = normalIC, rankIC    
        testIC  =testIC[:-1 ]
# 2. จาก ICT ให้ค้นหา [IC_Mean, IC_Std,IR,IC<0 สัดส่วน - ทิศทางปัจจัย,-IC->สัดส่วน 0.05]
        '''
        ICmean: |IC|>0.05,ปัจจัยมีความสามารถสูงในการเลือกเหรียญ และค่าปัจจัยมีความสัมพันธ์สูงกับอัตราผลตอบแทนของงวดถัดไป -เข้าใจแล้ว-<0.05,ความสามารถในการเลือกสกุลเงินของปัจจัยนั้นอ่อนแอ และความสัมพันธ์ระหว่างค่าปัจจัยและอัตราผลตอบแทนของงวดถัดไปต่ำ
        IR: |IR|>0.5,ความสามารถในการเลือกสกุลเงินของปัจจัยนั้นแข็งแกร่งและค่า IC ค่อนข้างคงที่ -ไออาร์-<0.5,ค่า IR น้อยเกินไปและปัจจัยไม่มีประสิทธิผลมากนัก หากเข้าใกล้ 0 ถือว่าไม่ถูกต้องโดยทั่วไป
IClZero (IC น้อยกว่าศูนย์): IC<0 คิดเป็นเกือบครึ่งหนึ่ง -> แฟกเตอร์เป็นกลาง IC>0 เกินมากกว่าครึ่งซึ่งเป็นปัจจัยลบ กล่าวคือ เมื่อค่าแฟกเตอร์เพิ่มขึ้น อัตราผลตอบแทนจะลดลง
        ICALzpF(IC abs large than zero poin five): |IC|>อัตราส่วน 0.05 อยู่ในระดับสูง บ่งชี้ว่าปัจจัยส่วนใหญ่มีประสิทธิผล
        '''
        IR = testIC.mean()/testIC.std()
        IClZero = testIC[testIC<0 ].count()/testIC.count() 
        ICALzpF = testIC[abs(testIC)>0.05 ].count()/testIC.count() 
        combined =pd.concat([testIC.mean(), testIC.std(), IR, IClZero, ICALzpF], axis= 1)
        combined.columns = ['ICmean','ICstd','IR','IClZero','ICALzpF']
# 3.IC แผนภูมิสะสมของ IC ในช่วงระยะเวลาการปรับสมดุล
        print("Test IC Table:")
        print(testIC)
        print("Result:")
        print('normal Skewness:', combined['normalIC'].skew(),'rank Skewness:', combined['rankIC'].skew())
        print('normal Skewness:', combined['normalIC'].kurt(),'rank Skewness:', combined['rankIC'].kurt())
        return combined, testIC.cumsum().plot()
2. การทดสอบค่า T (วิธีถดถอย)
วิธีค่า T ยังทดสอบความสัมพันธ์ระหว่างค่าตัวประกอบของงวดปัจจุบันกับอัตราผลตอบแทนของงวดถัดไป แต่จะแตกต่างจากวิธี ICIR ในการวิเคราะห์ความสัมพันธ์ระหว่างทั้งสอง วิธีค่า t ใช้อัตราของงวดถัดไปของ ส่งกลับเป็นตัวแปรตาม Y และค่าตัวประกอบของงวดปัจจุบันเป็นตัวแปรอิสระ X สำหรับการถดถอย X ให้ทำการทดสอบ t กับค่าสัมประสิทธิ์การถดถอยของค่าตัวประกอบการถดถอยเพื่อทดสอบว่าค่าดังกล่าวแตกต่างอย่างมีนัยสำคัญจาก 0 หรือไม่ นั่นคือว่า ปัจจัยงวดปัจจุบันส่งผลต่ออัตราผลตอบแทนของงวดถัดไป
สาระสำคัญของวิธีนี้คือการแก้แบบจำลองการถดถอยแบบสองตัวแปรโดยมีสูตรเฉพาะดังนี้


(1) ทฤษฎีวิธีถดถอย

(2) การใช้รหัสวิธีการถดถอย
def regT(self, chooseDate, factor, return_ 24 h):
        testT = pd.DataFrame(index=chooseDate, columns=['coef','T'])
        for i in range(len(chooseDate)-1):
            X = self.ini_data[self.ini_data['date'] == chooseDate[i]][factor].values
            Y = self.ini_data[self.ini_data['date'] == chooseDate[i+ 1 ]][return_ 24 h].values 
            b, intc = np.polyfit(X, Y,1) # ความชัน
            ut = Y - (b * X + intc) 
# ค้นหาค่า t = (\hat{b} - b) / se(b)
            n = len(X)
dof = n - 2 # องศาอิสระ
            std_b = np.sqrt(np.sum(ut** 2) / dof)
            t_stat = b / std_b
            testT.iloc[i] = b, t_stat
        testT = testT[:-1 ]
        testT_mean = testT['T'].abs().mean()
        testT L1 96 = len(testT[testT['T'].abs() > 1.96 ]) / len(testT)
        
        print('testT_mean:', testT_mean)
print(สัดส่วนของค่า T ที่มากกว่า 1.96:, testT L1 96)
        return testT
3. วิธีการทดสอบย้อนหลังแบบแบ่งชั้น
การแบ่งชั้นหมายถึงการแบ่งชั้นโทเค็นทั้งหมด และการทดสอบย้อนกลับหมายถึงการคำนวณอัตราการส่งคืนของแต่ละเลเยอร์ของการรวมโทเค็น
(1) การแบ่งชั้น
ขั้นแรก รับค่าตัวประกอบที่สอดคล้องกับพูลโทเค็น และเรียงลำดับโทเค็นตามค่าตัวประกอบ เรียงลำดับจากน้อยไปมาก นั่นคือค่าที่มีค่าตัวประกอบน้อยกว่าจะถูกจัดอันดับเป็นอันดับแรก และโทเค็นจะถูกแบ่งเท่าๆ กันตามการเรียงลำดับ ค่าตัวประกอบของโทเค็นเลเยอร์ 0 มีค่าน้อยที่สุด และค่าตัวประกอบของโทเค็นเลเยอร์ 9 มีค่ามากที่สุด
ตามทฤษฎีแล้ว การแบ่งเท่ากัน หมายถึงการแบ่งจำนวนโทเค็นให้เท่าๆ กัน กล่าวคือ จำนวนโทเค็นในแต่ละเลเยอร์จะเท่ากัน ซึ่งสามารถทำได้ด้วยความช่วยเหลือของควอนไทล์ ในความเป็นจริง จำนวนโทเค็นทั้งหมดไม่จำเป็นต้องเป็นผลคูณของจำนวนเลเยอร์ กล่าวคือ จำนวนโทเค็นในแต่ละเลเยอร์ไม่จำเป็นต้องเท่ากัน
(2) การทดสอบย้อนกลับ
หลังจากแบ่งโทเค็นออกเป็น 10 กลุ่มตามลำดับค่าตัวประกอบจากน้อยไปหามาก ให้เริ่มคำนวณอัตราการส่งคืนของการรวมโทเค็นแต่ละรายการ ขั้นตอนนี้ถือว่าโทเค็นของแต่ละเลเยอร์เป็นพอร์ตการลงทุน (โทเค็นที่มีอยู่ในการรวมโทเค็นของแต่ละเลเยอร์จะเปลี่ยนแปลงในช่วงระยะเวลาการทดสอบย้อนกลับที่แตกต่างกัน) และคำนวณมูลค่าโดยรวมของพอร์ตโฟลิโออัตราผลตอบแทนงวดถัดไป. ICIR และค่า t วิเคราะห์ค่าตัวประกอบปัจจุบันและอัตราผลตอบแทนโดยรวมในช่วงถัดไปแต่การทดสอบย้อนหลังแบบเป็นชั้นจำเป็นต้องมีการคำนวณอัตราผลตอบแทนพอร์ตโฟลิโอแบบแบ่งชั้นสำหรับแต่ละวันซื้อขายในช่วงระยะเวลาการทดสอบย้อนหลัง. เนื่องจากมีช่วงการทดสอบย้อนหลังหลายช่วงซึ่งมีหลายช่วง จึงจำเป็นต้องมีการแบ่งชั้นและการทดสอบย้อนกลับในแต่ละช่วง สุดท้าย อัตราผลตอบแทนโทเค็นของแต่ละเลเยอร์จะถูกคูณสะสมเพื่อคำนวณอัตราผลตอบแทนสะสมของการรวมโทเค็น
ตามหลักการแล้ว สำหรับปัจจัยที่ดี กลุ่ม 9 มีผลตอบแทนจากเส้นโค้งสูงสุด และกลุ่ม 0 มีผลตอบแทนจากเส้นโค้งต่ำที่สุด

เส้นโค้งสำหรับกลุ่ม 9 ลบกลุ่ม 0 (เช่น ผลตอบแทนระยะสั้น-ระยะยาว) กำลังเพิ่มขึ้นอย่างซ้ำซากจำเจ

(3) การใช้โค้ดของวิธีการทดสอบย้อนกลับแบบลำดับชั้น
def layBackTest(self, chooseDate, factor):
        f = {}
        returnM = {}
        for i in range(len(chooseDate)-1):
            df 1 = self.ini_data[self.ini_data['date'] == chooseDate[i]].rename(columns={'price':'close 0'})
            Y = pd.merge(df 1, self.ini_data[self.ini_data['date'] == chooseDate[i+ 1 ]][['date','name','price']], left_on=['name'], right_on=['name']).rename(columns={'price':'close 1'})
            f[i] = Y[factor]
            returnM[i] = Y['close 1'] / Y['close 0'] -1 
        labels = ['0','1','2','3','4','5','6','7','8','9']
        res = pd.DataFrame(index=['0','1','2','3','4','5','6','7','8','9','LongShort'])
        res[chooseDate[ 0 ]] = 1 
        for i in range(len(chooseDate)-1):
            dfM = pd.DataFrame({'factor':f[i],'returnM':returnM[i]})
            dfM['group'] = pd.qcut(dfM['factor'], 10, labels=labels)
            dfGM = dfM.groupby('group').mean()[['returnM']]
dfGM.loc[LongShort] = dfGM.loc[0]- dfGM.loc[9]res[chooseDate[i+ 1 ]] = res[chooseDate[ 0 ]] * ( 1 + dfGM[returnM ]) data = pd.DataFrame({ผลตอบแทนสะสมตามลำดับชั้น:res.iloc[: 10,-1],กลุ่ม:[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]})
        df 3 = data.corr()
        print("Correlation Matrix:")
        print(df 3)
        return res.T.plot(title='Group backtest net worth curve')


