# 用多因子策略构建强大的加密资产投资组合 #数据预处理篇#

By [LUCIDA & FALCON](https://paragraph.com/@lucidafund) · 2023-11-28

---

前言
--

书接上回，我们发布了[《用多因子策略构建强大的加密资产投资组合》系列文章的第一篇 - 理论基础篇](https://mirror.xyz/lucidafund.eth/hx3yQmO7aws8zco7qFMLGGKf6WPqfpXDpVVcwVvDPUk)，本篇是第二篇 - 数据预处理篇。

在计算因子数据前/后，以及测试单因子的有效性之前，都需要对相关数据进行处理。具体的数据预处理涉及**重复值、异常值/缺失值/极端值、标准化和数据频率**的处理。

一、重复值
-----

### 数据相关定义：

*   键（Key）：表示一个独一无二的索引。eg. 对于一份有全部token所有日期的数据，键是“token\_id/contract\_address - 日期”
    
*   值（Value）：被键索引的对象就称之为“值”。
    

诊断重复值的首先需要理解数据“应当”是什么样子。通常数据的形式有：

1.  时间序列数据（Time Series）。键是“时间”。eg.单个token5年的价格数据
    
2.  横截面数据（Cross Section)。键是“个体”。eg.2023.11.01当日crypto市场所有token的价格数据
    
3.  面板数据（Panel）。键是“个体-时间”的组合。eg.从2019.01.01-2023.11.01 四年所有token的价格数据。
    

原则：确定了数据的索引（键），就能知道数据应该在什么层面没有重复值。

### 检查方式：

1.  `pd.DataFrame.duplicated(subset=[key1, key2, ...])`
    
    1.  检查重复值的数量：`pd.DataFrame.duplicated(subset=[key1, key2, ...]).sum()`
        
    2.  抽样看重复的样本：`df[df.duplicated(subset=[...])].sample()`找到样本后，再用`df.loc`选出该索引对应的全部重复样本
        
2.  `pd.merge(df1, df2, on=[key1, key2, ...], indicator=True, validate='1:1')`
    
    1.  在横向合并的函数中，加入`indicator`参数，会生成`_merge`字段，对其使用`dfm['_merge'].value_counts()`可以检查合并后不同来源的样本数量
        
    2.  加入`validate`参数，可以检验合并的数据集中索引是否如预期一般（`1 to 1`、`1 to many`或`many to many`，其中最后一种情况其实等于不需要验证）。如果与预期不符，合并过程会报错并中止执行。
        

**二、异常值/缺失值/极端值**
-----------------

### **产生异常值的常见原因：**

1.  **极端情况**。比如token价格0.000001$或市值仅50万美元的token，随便变动一点，就会有数十倍的回报率。
    
2.  **数据特性**。比如token价格数据从2020年1月1日开始下载，那么自然无法计算出2020年1月1日的回报率数据，因为没有前一日的收盘价。
    
3.  **数据错误**。数据提供商难免会犯错，比如将12元每token记录成1.2元每token。
    

### **针对异常值和缺失值处理原则：**

1.  删除。对于无法合理更正或修正的异常值，可以考虑删除。
    
2.  替换。通常用于对极端值的处理，比如缩尾（Winsorizing）或取对数（不常用）。
    
3.  填充。对于**缺失值**也可以考虑以合理的方式填充，常见的方式包括均**值（或移动平均）、插值（Interpolation）、填0** `df.fillna(0)`、向前`df.fillna('ffill')`/向后填充`df.fillna('bfill')`等，要考虑填充所依赖的假设是否合。
    
    机器学习慎用向后填充，有 Look-ahead bias 的风险
    

### **针对极端值的处理方法：**

1.百分位法。

通过将顺序从小到大排列，将超过最小和最大比例的数据替换为临界的数据。对于历史数据较丰富的数据，该方法相对粗略，不太适用，强行删除固定比例的数据可能造成一定比例的损失。

2.3σ / 三倍标准差法

标准差 $$σfactor$$ 体现因子数据分布的离散程度，即波动性。利用 $$μ±3×σ$$ 范围识别并替换数据集中的异常值，约有99.73% 的数据落入该范围。该方法适用前提：**因子数据必须服从正态分布**，即 $$X∼N(μ,σ2)$$。

其中，$$μ=∑ⁿᵢ₌₁·Xi / N$$, $$σ²=∑ⁿᵢ₌₁(xi-μ)² / n$$ ，因子值的合理范围是$$ \[μ-3×σ,μ+3×σ\]$$。

对数据范围内的所有因子做出如下调整：

![](https://storage.googleapis.com/papyrus_images/bccc64fb1b2a21ffa9c85780828b0d349fd33140311c9438908ebe058f975a73.png)

该方法不足在于，量化领域常用的数据如股票价格、token价格常呈现尖峰厚尾分布，并不符合正态分布的假设，在该情况下采用$$3σ$$方法将有大量数据错误地被识别为异常值。

3.绝对值差中位数法（Median Absolute Deviation, MAD）

该方法基于中位数和绝对偏差，使处理后的数据对极端值或异常值没那么敏感。比基于均值和标准差的方法更稳健。

绝对偏差值的中位数 $$MAD = median( ∑ⁿᵢ₌₁ (Xi - Xmedian))$$

因子值的合理范围是$$\[Xmedian - n×MAD, Xmedian + n×MAD\]$$。对数据范围内的所有因子做出如下调整：

![](https://storage.googleapis.com/papyrus_images/628355510d845723a6257df0a1be2cb665ef7fb95d52b3907c5d6da809bac970.png)

    # 处理因子数据极端值情况
    class Extreme(object):
        def __init__(s, ini_data):
            s.ini_data = ini_data
    
        def three_sigma(s,n=3):
            mean = s.ini_data.mean()
            std = s.ini_data.std()
            low = mean - n*std
            high = mean + n*std
            return np.clip(s.ini_data,low,high)
    
        def mad(s, n=3):
            median = s.ini_data.median()
            mad_median = abs(s.ini_data - median).median()
            high = median + n * mad_median
            low = median - n * mad_median
            return np.clip(s.ini_data, low, high)
    
        def quantile(s,l = 0.025, h = 0.975):
            low = s.ini_data.quantile(l)
            high = s.ini_data.quantile(h)
            return np.clip(s.ini_data, low, high)
    

**三、标准化**
---------

1.Z-score标准化

*   前提：$$X ~ N(μ,σ)$$
    
*   由于使用了标准差，该方法对于数据中的异常值较为敏感
    

$$x’ᵢ = (x-μ) / σ = (x - mean(X)) / std(X)$$

2.最大最小值差标准化（Min-Max Scaling）

将每个因子数据转化为在$$(0,1)$$ 区间的数据，以便比较不同规模或范围的数据，但它并不改变数据内部的分布，也不会使总和变为1。

*   由于考虑极大极小值，对异常值敏感
    
*   **统一量纲**，利于比较不同维度的数据。
    

$$x'ᵢ = (xᵢ - min(x)) / max(x) - min(x)$$

3.排序百分位（Rank Scaling）

将数据特征转换为它们的排名，并将这些排名转换为介于0和1之间的分数，通常是它们在数据集中的百分位数。\*

*   **由于排名不受异常值影响，该方法对异常值不敏感。**
    
*   不保持数据中各点之间的绝对距离，而是转换为相对排名。
    

$$NormRankᵢ = (Rankₓᵢ - min(Rankₓᵢ)) / max(Rankₓ) - min(Rankₓ) = Rankₓᵢ / N$$

其中，$$min(Rankₓ)=0$$， $$N$$ 为区间内数据点的总个数。

    # 标准化因子数据
    class Scale(object):
        def __init__(s, ini_data,date):
            s.ini_data = ini_data
            s.date = date
        
        def zscore(s):
            mean = s.ini_data.mean()
            std = s.ini_data.std()
            return s.ini_data.sub(mean).div(std)
          
        def maxmin(s):
            min = s.ini_data.min()
            max = s.ini_data.max()
            return s.ini_data.sub(min).div(max - min)
    
        def normRank(s):
            # 对指定列进行排名，method='min'意味着相同值会有相同的排名，而不是平均排名
            ranks = s.ini_data.rank(method='min') 
            return ranks.div(ranks.max())
    

**四、数据频率**
----------

有时获得的数据并非我们分析所需要的频率。比如分析的层次为月度，原始数据的频率为日度，此时就需要用到“下采样”，即聚合数据为月度。

### **下采样**

指的是**将一个集合里的数据聚合为一行数据**，比如日度数据聚合为月度。此时需要考虑每个被聚合的指标的特性，通常的操作有：

*   第一个值/最后一个值
    
*   均值/中位数
    
*   标准差
    

### **上采样**

指的是将一行数据的数据拆分为多行数据，比如年度数据用在月度分析上。这种情况一般就是简单重复即可，有时需要将年度数据按比例归集于各个月份。

### **关于LUCIDA & FALCON**

Lucida （[https://www.lucida.fund/](https://www.lucida.fund/) ）是行业领先的量化对冲基金，在2018年4月进入Crypto市场，主要交易CTA / 统计套利 / 期权波动率套利等策略，现管理规模3000万美元。

Falcon （[https://falcon.lucida.fund](https://falcon.lucida.fund/) /）是新一代的Web3投资基础设施，它基于多因子模型，帮助用户“选”、“买”、“管”、“卖”加密资产。Falcon在2022年6月由Lucida所孵化。

更多内容可访问 [https://linktr.ee/lucida\_and\_falcon](https://linktr.ee/lucida_and_falcon)

### **往期文章**

*   [用多因子策略构建强大的加密资产投资组合 #理论基础篇#](https://mirror.xyz/lucidafund.eth/hx3yQmO7aws8zco7qFMLGGKf6WPqfpXDpVVcwVvDPUk)
    
*   [From Tech Breakthroughs to Market Boom: Understanding the Link in the Crypto Bull Market](https://mirror.xyz/lucidafund.eth/tnF4j6Xq34QXnv9TYrlAk4nDU9z9JvUMJfU_hTNfbsI)
    
*   [到底是什么在驱动着Crytpo的牛市？是技术的升级吗？](https://mirror.xyz/lucidafund.eth/ZUbquhIfl0qJ_AOtnKWNnC4DJ5GpzECy7jvtsJr32F0)
    
*   [Development as the Driving Force: Understanding the Impact on Token Price Performance?](https://mirror.xyz/lucidafund.eth/bugGmA-Gy64xIzn4_PJ2PaGtLx9SqPxu5BkxV7aM1Mg)
    
*   [“团队在做事”和币价真的有关吗？](https://mirror.xyz/lucidafund.eth/CqoDGa_YcveRIm35fWMJX6nI67tfec1UCzrqwImSIaY)
    
*   [500万行数据盘点Crypto三年大牛市 @LUCIDA](https://mirror.xyz/lucidaquant.eth/6wu0au7fFQShR2R76sUTDXXnH-ma8MA6FNBKXeX4vTM)
    
*   [_5 Million Rows of Data Recap: Investigating The Crypto Market’s 3-Year Bull Run @LUCIDA_](https://mirror.xyz/lucidaquant.eth/2vRVzG_8KMku5WFA_ddYUPSe6-7lOabKnYYFFm--pto)
    
*   [LUCIDA：用多因子模型去选赛道、选币种](https://mirror.xyz/lucidafund.eth/UdOfxxKgD_Xuc_KrvGvsjrWZZCwKlWPAYNx991ZgmIA)
    
*   [LUCIDA × SnapFingers DAO：三年大牛市，21条头部公链大盘点](https://mirror.xyz/lucidafund.eth/HEXSOV4cV_AsuzdbrxUWiqzoMhiQ12Q7tBHsKSm3ki4)
    
*   [LUCIDA × SnapFingers DAO：21 top public chains in three-year bull market recap](https://mirror.xyz/lucidafund.eth/xcYLVmolc9f4Gax07J3ROoGBxEBC-RFJzSL2-R5EYGA)
    
*   [LUCIDA × OKLink：链上数据对二级市场投资的价值](https://mirror.xyz/lucidafund.eth/DPoha70KcM1wyF3VLmAmaDHO8M8oxjlQJ9UhZXguKmU)

---

*Originally published on [LUCIDA & FALCON](https://paragraph.com/@lucidafund/z4ePESgalEzVbfPq8PpN)*
