作者: 阿布
阿布量化版權所有 未經允許 禁止轉載
我曾經編寫過豬老三的世界中使用機器學習對股價和漲跌進行預測的幻想示例:
在豬老三的世界中實現了:機器學習.fit(x, y) = (股價預測,漲跌預測) =發財
在豬老三的童話中我設定的可以影響股價走勢的引數是有限個的,特別是影響漲跌的因素很容易構造特徵。在真實的市場中可以影響股價走勢的因素是無限多的,而且這些因素之間的也可以是相關的。就像你求解一個方程組,這個方程組不是有一個兩個解,它是有無限個解的系統,並且每個解都與其他任意個解相關,但又並非簡單線性相關。市場是一個二級混沌系統,認為任何想通過技術對股價進行預測或者漲跌預測都是不可能的,不倫你自己認為你使用的技術本身有多高深,無異於管中窺豹。
本系列教程中講到的ump裁判系統是abupy中通過機器學習技術對回測結果進行學習,反向指導決策新的交易是否攔截的實際應用,本節講解機器學習在量化領域很有作用的另一個方向:閥值的估計,因為無論是編寫選股策略,擇時策略還是任何涉及決策的程式碼模型中都離不開閥值,比如最常用的止盈止損策略,在程式碼的編寫中就一定會涉及閥值,比如之前的章節一直使用的abupy中內建止盈止損策略AbuFactorCloseAtrNStop。
之所以一定會涉及閥值的確定是因為就像剛剛說的類似你求解一個方程組,如果所有的引數都是未知數,那麼你怎麼解出你需要的答案,所以一定要把一些變數變成常數值,然後通過這些常數值來確定更多的變數,最終解出你所關心的解。
對於閥值的確定傳統的做法是根據經驗來設定,實際上所謂的經驗是個體對問題的統計模型, 在機器學習技術的幫助下,可以實現更客觀,全面,適應範圍更廣的閥值設定。
不論是個體經驗對閥值進行常量定估還是通過機器學習技術通過資料模型對閥值進行定估,都達不到絕對準確預測結果的目標,量化交易的概率優勢並不具有絕對的優勢,即達不到預測的程度,量化交易中大多策略是基於對歷史規律的總結,在規律的基礎上發現概率優勢,它的最大理論依據是人性的相似性以及人性很難改變的事實,如果每一個瞬間的股票價格都是全體交易者對價值所達成的一種瞬間共識,那麼歷史的規律在今後的交易中同樣具有指導意義。
按照上面解方程的說法就是,定估的常量只要能滿足大多數時候解出合理的解,甚至很多時候定估的常量只要能滿足有時候能解出合理的解就可以,對於有時候能解出合理的情況,可以在上層通過非均衡技術對決策進行二次邏輯過濾,我反覆提及過的非均衡技術思想是量化中很很重要的一種設計思路,因為我們量化的目標結果就是非均衡,我們想要贏的錢比輸的多。
1. 比特幣特徵的提取
下面通過對比特幣的短線交易決策為例,示例上述論點以及abupy中機器學習模組的使用,以及資料獲取:
from abupy import abu, ml, nd, tl, pd_resample, AbuML, AbuMLPd, AbuMetricsBase
from abupy import ABuSymbolPd, ABuScalerUtil, get_price, ABuMarketDrawing, ABuKLUtil
# btc是比特幣symbol代號
btc = ABuSymbolPd.make_kl_df('btc', start='2013-09-01', end='2017-07-26')複製程式碼
之前在比特幣, 萊特幣的回測那節使用ABuKLUtil.date_week_wave對走勢的日震盪做過統計如下:
ABuKLUtil.date_week_wave(btc)複製程式碼
date_week
週一 5.0108
週二 5.5610
週三 5.4437
週四 5.7275
週五 5.3008
週六 4.7875
週日 4.6528
Name: wave, dtype: float64複製程式碼
從上面可以看出大概0.055的日震盪幅度可以成做大波動的交易對比特幣來說,下面對資料新增新列big_wave,可以看到結果大波動的佔總交易日的1/3:
btc['big_wave'] = (btc.high - btc.low) / btc.pre_close > 0.055
btc['big_wave'] = btc['big_wave'].astype(int)
btc['big_wave'].value_counts()複製程式碼
0 1005
1 420
Name: big_wave, dtype: int64複製程式碼
任何大的決策其實都是由很多看極起來極不起眼的小事組成的,如果我們是做比特幣日內的交易者,首先你需要判斷今天適不適合做交易,做出這個判斷的依據裡有一條即是今天的波動需要足夠大,下面通過機器學習技術演示如何決策今天的波動是否足夠大。
備註:由於abupy中內建沙盒資料沒有分時的資料,所以本示例使用日線資料做為分析物件,實際策略中應該使用的是分鐘資料
首先切割訓練集和測試集,保留最後60天走勢資料做為測試集資料:
btc_train_raw = btc[:-60]
btc_test_raw = btc[-60:]複製程式碼
下面為訓練集和測試集資料都加上5,10,21,60日均線特徵:
def calc_ma(tc, ma):
ma_key = 'ma{}'.format(ma)
tc[ma_key] = nd.ma.calc_ma_from_prices(tc.close, ma, min_periods=1)
for ma in [5, 10, 21, 60]:
calc_ma(btc_train_raw, ma)
calc_ma(btc_test_raw, ma)
btc_train_raw.tail(1)複製程式碼
編寫特徵抽取組合函式btc_siblings_df:
- 它首先將所有交易日以3個為一組,切割成多個子df,即每一個子df中有3個交易日的交易資料
- 使用資料標準化將連續3天交易日中的連續數值特徵進行標準化操作
- 抽取第一天,第二天的大多數特徵分別改名字以one,two為特徵字首,如:one_open,one_close,two_ma5,two_high.....,
- 第三天的特徵只使用'open', 'low', 'pre_close', 'date_week',該名字首today,如today_open,today_date_week
- 第三天的抽取了'big_wave',其將在之後做為y
- 將抽取改名字後的特徵連線起來組合成為一條新資料,即3天的交易資料特徵->1條新的資料
程式碼如下所示:
def btc_siblings_df(btc_raw):
# 將所有交易日以3個為一組,切割成多個子df,即每一個子df中有3個交易日的交易資料
btc_siblings = [btc_raw.ix[sib_ind * 3:(sib_ind + 1) * 3, :]
for sib_ind in np.arange(0, int(btc_raw.shape[0] / 3))]
btc_df = pd.DataFrame()
for sib_btc in btc_siblings:
# 使用資料標準化將連續3天交易日中的連續數值特徵進行標準化操作
sib_btc_scale = ABuScalerUtil.scaler_std(
sib_btc.filter(['open', 'close', 'high', 'low', 'volume', 'pre_close',
'ma5', 'ma10', 'ma21', 'ma60', 'atr21', 'atr14']))
# 把標準化後的和big_wave,date_week連線起來
sib_btc_scale = pd.concat([sib_btc['big_wave'], sib_btc_scale, sib_btc['date_week']], axis=1)
# 抽取第一天,第二天的大多數特徵分別改名字以one,two為特徵字首,如:one_open,one_close,two_ma5,two_high.....
a0 = sib_btc_scale.iloc[0].filter(['open', 'close', 'high', 'low', 'volume', 'pre_close',
'ma5', 'ma10', 'ma21', 'ma60', 'atr21', 'atr14', 'date_week'])
a0.rename(index={'open': 'one_open', 'close': 'one_close', 'high': 'one_high', 'low': 'one_low',
'volume': 'one_volume', 'pre_close': 'one_pre_close',
'ma5': 'one_ma5', 'ma10': 'one_ma10', 'ma21': 'one_ma21',
'ma60': 'one_ma60', 'atr21': 'one_atr21', 'atr14': 'one_atr14',
'date_week': 'one_date_week'}, inplace=True)
a1 = sib_btc_scale.iloc[1].filter(['open', 'close', 'high', 'low', 'volume', 'pre_close',
'ma5', 'ma10', 'ma21', 'ma60', 'atr21', 'atr14', 'date_week'])
a1.rename(index={'open': 'two_open', 'close': 'two_close', 'high': 'two_high', 'low': 'two_low',
'volume': 'two_volume', 'pre_close': 'two_pre_close',
'ma5': 'two_ma5', 'ma10': 'two_ma10', 'ma21': 'two_ma21',
'ma60': 'two_ma60', 'atr21': 'two_atr21', 'atr14': 'two_atr14',
'date_week': 'two_date_week'}, inplace=True)
# 第三天的特徵只使用'open', 'low', 'pre_close', 'date_week',該名字首today,如today_open,today_date_week
a2 = sib_btc_scale.iloc[2].filter(['big_wave', 'open', 'low', 'pre_close', 'date_week'])
a2.rename(index={'open': 'today_open', 'low': 'today_low',
'pre_close': 'today_pre_close',
'date_week': 'today_date_week'}, inplace=True)
# 將抽取改名字後的特徵連線起來組合成為一條新資料,即3天的交易資料特徵->1條新的資料
btc_df = btc_df.append(pd.concat([a0, a1, a2], axis=0), ignore_index=True)
return btc_df複製程式碼
第三天的特徵避免使用high是因為訓練的目標y是big_wave,之所以可以使用當天的low是因為比特幣市場的特點為24小時交易,即沒有明確的一天的概念,也即沒有明確一天中的最低,實盤使用即人工對當前最低根據24小時最低進行猜測,或直接使用24小時最低,或者使用當前的最新價格都可,組成資料後使用模型進行決策,決策的結果即為未來數小時內是否會有大的波動,實際上最終大波動的決策成立,需要其它很多模型共同生效,比如外盤的走勢決策等等。
下面使用訓練集資料btc_train_raw做為引數抽取組合特徵,重新組合好的特徵如tail所示:
btc_train0 = btc_siblings_df(btc_train_raw)
btc_train0.tail()複製程式碼
如上所示這樣我們只能得到454條訓練集資料,但由於每3條連續交易日資料組合成一個特徵,只要向前跳一條資料進行特徵組合抽取即可以得到另一組新特徵,下面分別跳過第一個,第二個資料,抽取btc_train1,btc_train2:
btc_train1 = btc_siblings_df(btc_train_raw[1:])
btc_train2 = btc_siblings_df(btc_train_raw[2:])複製程式碼
下面把上面的3組特徵連起來,然後把周幾這個特徵使用pd.get_dummies進行離散化處理,使得所有特徵值的範圍都在0-1之間,最終的特徵如下btc_train所示:
btc_train = pd.concat([btc_train0, btc_train1, btc_train2])
btc_train.index = np.arange(0, btc_train.shape[0])
dummies_one_week = pd.get_dummies(btc_train['one_date_week'], prefix='one_date_week')
dummies_two_week = pd.get_dummies(btc_train['two_date_week'], prefix='two_date_week')
dummies_today_week = pd.get_dummies(btc_train['today_date_week'], prefix='today_date_week')
btc_train.drop(['one_date_week', 'two_date_week', 'today_date_week'], inplace=True, axis=1)
btc_train = pd.concat([btc_train, dummies_one_week, dummies_two_week, dummies_today_week], axis=1)
pd.options.display.max_rows=10
btc_train複製程式碼
2. abu中內建機器學習模組的使用
下面使用abupy中內建機器學習工具AbuML對特徵資料進行封裝,程式碼如下所示,下面的y值即是big_wave列:
train_matrix = btc_train.as_matrix()
y = train_matrix[:, 0]
x = train_matrix[:, 1:]
btc_ml = AbuML(x, y, btc_train)複製程式碼
AbuML會根據資料特點自動內部選擇使用分類器或者回歸器,下面進行特指最優分類器操作,比如特指使用隨機森林做為分類器,執行random_forest_classifier_best會在內部對n_estimators引數和訓練集資料進行grid search擬合,尋找最合適的引數最終做為內部分類器:
btc_ml.random_forest_classifier_best()複製程式碼
start grid search please wait...複製程式碼
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
max_depth=None, max_features='auto', max_leaf_nodes=None,
min_impurity_split=1e-07, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0,
n_estimators=260, n_jobs=1, oob_score=False, random_state=None,
verbose=0, warm_start=False)複製程式碼
所有best函式中尋找最優引數內部只根據學習器的特點尋找最少量的引數設定,如果需要自定義引數範圍可使用如下所示:
param_grid = {'max_features': ['sqrt', 'log2'], 'n_estimators': np.arange(50, 500, 50)}
btc_ml.random_forest_classifier_best(param_grid=param_grid)複製程式碼
start grid search please wait...複製程式碼
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
max_depth=None, max_features='sqrt', max_leaf_nodes=None,
min_impurity_split=1e-07, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0,
n_estimators=400, n_jobs=1, oob_score=False, random_state=None,
verbose=0, warm_start=False)複製程式碼
下面使用btc_ml對訓練集進行交叉準確率評分:
btc_ml.cross_val_accuracy_score()複製程式碼
RandomForestClassifier score mean: 0.8151620867325032
array([ 0.781 , 0.8102, 0.7883, 0.8382, 0.8162, 0.8162, 0.8235,
0.8456, 0.7794, 0.8529])複製程式碼
下面使用btc_ml對訓練集進行交叉roc_auc評分:
btc_ml.cross_val_roc_auc_score()複製程式碼
RandomForestClassifier score mean: 0.8399573797130188
array([ 0.815 , 0.8785, 0.8166, 0.8018, 0.8707, 0.8484, 0.8148,
0.8551, 0.8005, 0.8981])複製程式碼
AbuML對外的函式都支援關鍵子引數fiter_type,可以指定使用的學習器型別如迴歸,聚類等,每個函式都通過內部裝飾器宣告自己支援的學習器型別對不支援的型別輸出不支援,如下想通過指定使用迴歸器進行roc_auc評分:
btc_ml.cross_val_roc_auc_score(fiter_type=ml.EMLFitType.E_FIT_REG)複製程式碼
cross_val_roc_auc_score not support reg!複製程式碼
上述輸出顯示函式不支援迴歸器,因為內部實現中通過entry_wrapper裝飾器宣告瞭只支援E_FIT_CLF,即分類器:
@entry_wrapper(support=(EMLFitType.E_FIT_CLF,))
def cross_val_roc_auc_score(self, cv=10, **kwargs):
"""
被裝飾器entry_wrapper(support=(EMLFitType.E_FIT_CLF,))裝飾,
即支援有監督學習分類,使用cross_val_score對資料進行roc_auc度量,如果資料的y的
label標籤 > 2,通過label_binarize將label標籤進行二值化處理,
依次計算二值化的列的roc_auc,結果返回score最好的資料度量
:param cv: 透傳cross_val_score的引數,預設10
:param kwargs: 外部可以傳遞x, y, 通過
x = kwargs.pop('x', self.x)
y = kwargs.pop('y', self.y)
確定傳遞self._do_cross_val_score中引數x,y,
以及裝飾器使用的fiter_type,eg:
ttn_abu.cross_val_roc_auc_score(fiter_type=ml.EMLFitType.E_FIT_REG)
:return: cross_val_score返回的score序列,
eg: array([ 1. , 0.9 , 1. , 0.9 , 1. , 0.9 , 1. , 0.9 , 0.95, 1. ])
"""
x = kwargs.pop('x', self.x)
y = kwargs.pop('y', self.y)
return self._do_cross_val_score(x, y, cv, _EMLScoreType.E_SCORE_ROC_AUC.value)複製程式碼
更多詳情實現請閱讀原始碼AbuML
下面使用train_test_split_xy檢視訓練集輸出的precision_score,recall_score,混淆矩陣,以及f1等度量結果:
btc_ml.train_test_split_xy()複製程式碼
x-y:(1363, 48)-(1363,)
train_x-train_y:(1226, 48)-(1226,)
test_x-test_y:(137, 48)-(137,)
accuracy = 0.77
precision_score = 0.62
recall_score = 0.39
Predicted
| 0 | 1 |
|-----|-----|
0 | 90 | 9 |
Actual |-----|-----|
1 | 23 | 15 |
|-----|-----|
precision recall f1-score support
0.0 0.80 0.91 0.85 99
1.0 0.62 0.39 0.48 38
avg / total 0.75 0.77 0.75 137複製程式碼
下面通過plot_roc_estimator繪製roc曲線:
btc_ml.plot_roc_estimator()複製程式碼
下面通過plot_confusion_matrices繪製混淆矩陣:
btc_ml.plot_confusion_matrices()複製程式碼
[[915 65]
[183 200]]複製程式碼
下面通過plot_decision_function繪製決策邊界,由於繪製2d圖,內部已經使用pca將資料特徵降維後再繪製:
btc_ml.plot_decision_function()複製程式碼
下面通過plot_graphviz_tree繪製邏輯決策圖:
btc_ml.plot_graphviz_tree()複製程式碼
RandomForestClassifier not hasattr tree_, use decision tree replace複製程式碼
下面通過feature_selection對特徵的支援度進行評級:
pd.options.display.max_rows = 48
btc_ml.feature_selection(show=False).sort_values(by='ranking')複製程式碼
下面通過importances_coef_pd對特徵的重要程度進行量化:
btc_ml.importances_coef_pd().sort_values(by='importance')[::-1]複製程式碼
3. 測試集的驗證與非均衡技術
下面將前面保留切割的60條測試資料進行特徵抽取組合,方式和抽取訓練集時一樣,程式碼如下所示:
btc_test0 = btc_siblings_df(btc_test_raw)
btc_test1 = btc_siblings_df(btc_test_raw[1:])
btc_test2 = btc_siblings_df(btc_test_raw[2:])
btc_test = pd.concat([btc_test0, btc_test1, btc_test2])
btc_test.index = np.arange(0, btc_test.shape[0])
dummies_one_week = pd.get_dummies(btc_test['one_date_week'], prefix='one_date_week')
dummies_two_week = pd.get_dummies(btc_test['two_date_week'], prefix='two_date_week')
dummies_today_week = pd.get_dummies(btc_test['today_date_week'], prefix='today_date_week')
btc_test.drop(['one_date_week', 'two_date_week', 'today_date_week'], inplace=True, axis=1)
btc_test = pd.concat([btc_test, dummies_one_week, dummies_two_week, dummies_today_week], axis=1)
matrix_test = btc_test.as_matrix()
y_test = matrix_test[:, 0]
x_test = matrix_test[:, 1:]複製程式碼
對測試集資料進行準確率評估,程式碼如下所示:
from sklearn import metrics
y_predict = [btc_ml.predict(x_test[test_ind])[0] for test_ind in np.arange(0, matrix_test.shape[0])]
print('測試集正確率{:3f}'.format(metrics.accuracy_score(y_test, y_predict)))複製程式碼
測試集正確率0.620690複製程式碼
如上所示上面的準確率結果為60%以上正確,下面使用predict_proba看看概率結果:
y_prob = [btc_ml.predict_proba(x_test[test_ind])[0] for test_ind in np.arange(0, matrix_test.shape[0])]複製程式碼
y_prob[-10:]複製程式碼
[array([ 0.495, 0.505]),
array([ 0.9075, 0.0925]),
array([ 0.7875, 0.2125]),
array([ 0.83, 0.17]),
array([ 0.8375, 0.1625]),
array([ 0.96, 0.04]),
array([ 0.58, 0.42]),
array([ 0.495, 0.505]),
array([ 0.6575, 0.3425]),
array([ 0.565, 0.435])]複製程式碼
本節開始的時候說過很多時候定估的決策只要能滿足有時候能解出合理的解就可以,通過非均衡技術對決策進行二次邏輯過濾即可,下面解釋這句話的意思,通過上面y_predict輸出你可以看到準確率結果為60%以上,如果你只按照這個決策認定比特幣今天是否有大行情還是有很多錯誤的可能,上面的y_prob輸出了每一個決策的概率。
那麼我們如果希望做比特幣交易只希望在決策模型有很大把握的情況下才做,否則寧可少賺點錢也不做應該怎麼做呢?
上面predict的決策在某一個值在0.5以上即進行了判斷,如果我們希望把握更大一些就需要調整這個值,比如0.55以上才能認定決策,那首要的問題就是如何選定這個閥值,下面示例使用search_match_pos_threshold進行閥值的確定:
btc_ml.search_match_pos_threshold(accuracy_match=0.85)複製程式碼
0.580 satisfy require, accuracy:0.854, effect_rate:0.879複製程式碼
結果如上所示,search_match_pos_threshold函式的作用是對訓練集資料進行非均衡結果度量,從0.5至0.99的範圍內開始不斷向上調整決策閥值:
比如測試閥值為0.55,那麼如果決策的概率為array([ 0.5378, 0.4622]),那麼通過閥值二分化後結果為(0, 0),這個結果將不計入正確率統計中,即認為未達成有效的決策,但如果決策的概率為 array([ 0.9711, 0.0289]),那麼通過閥值二分化後結果為(1, 0),認為達成有效的決策,當有效的決策正確率達到引數accuracy_match傳遞的值,本例中使用0.85即85%的正確率時,停止搜尋,本例返回的結果為0.580,即使用閥值0.580可以達成非均衡決策的85%正確率。
備註:與之對應search_match_neg_threshold進行閥值搜尋,從0.5至0.01的範圍內開始不斷向下調整決策閥值,更多詳情請閱讀原始碼。
下面使用0.580做為閥值,通過predict_proba_threshold函式進行決策,程式碼如下:
y_prob_threshold = [btc_ml.predict_proba_threshold(x_test[test_ind], 0.580, 0)
for test_ind in np.arange(0, matrix_test.shape[0])]複製程式碼
上面predict_proba_threshold傳遞的第三個引數0為未達成有效決策的情況下返回的決策結果,即閥值二分化後結果為(0, 0)後這裡返回0,因為在比特幣這個示例中如果交易者想要保守的方式決策今天是否適合做日內交易,那麼就希望只在有很大概率的情況下返回1,即適合交易,其它情況下沒有太大把握下全部返回0即可,下面使用precision_score計算查準率:
metrics.precision_score(y_test, y_prob_threshold)複製程式碼
1.0複製程式碼
如上所示查準率100%正確,但召回率評分非常低,即比特幣在很多有大行情的情況下為了保守,放棄了日交易,只在把握大的時候行動:
metrics.recall_score(y_test, y_prob_threshold)複製程式碼
0.20000000000000001複製程式碼
與上面的情況相反也有些激進的交易者想要的決策結果是隻要今天不是很大把握說沒有行情,那就進行交易。
下面使用0.90做為閥值,通過predict_proba_threshold函式進行決策,第三個引數1即在為未達成有效決策的情況下返回的決策結果為1,可以看到結果的決策中大多數都被決策為1,程式碼如下:
y_prob_threshold = [btc_ml.predict_proba_threshold(x_test[test_ind], 0.90, 1)
for test_ind in np.arange(0, matrix_test.shape[0])]
pd.Series(y_prob_threshold).value_counts()複製程式碼
1 49
0 9
dtype: int64複製程式碼
4. 繼承AbuMLPd對資料處理進行封裝
在abupy中不建議直接使用AbuML類進行構造,推薦使用繼承AbuMLPd後實現make_xy方法,在make_xy中將資料訓練集和測試集進行組裝完成,AbuMLPd基類通過代理方法對AbuML中的所有方法進行代理,即可以和使用AbuML中的方法一樣的介面操作,本節使用的示例內建在abupy專案中BtcBigWaveClf類,具體實現請直接閱讀BtcBigWaveClf。
小結:由於abupy中內建沙盒資料沒有分時的資料,所以本示例使用日線資料做為分析物件,實際策略中應該使用的是分鐘資料,本節示例主要為配合abupy中機器學習模組的使用示例所寫,與真實的日內交易決策還有很大差別,在後面的章節涉及到實盤交易章節會具體詳細的示例完整的一個日內交易策略,請關注公眾號的更新提醒。
abu量化文件目錄章節
- 擇時策略的開發
- 擇時策略的優化
- 滑點策略與交易手續費
- 多支股票擇時回測與倉位管理
- 選股策略的開發
- 回測結果的度量
- 尋找策略最優引數和評分
- A股市場的回測
- 港股市場的回測
- 比特幣,萊特幣的回測
- 期貨市場的回測
- 機器學習與比特幣示例
- 量化技術分析應用
- 量化相關性分析應用
- 量化交易和搜尋引擎
- UMP主裁交易決策
- UMP邊裁交易決策
- 自定義裁判決策交易
- 資料來源
- A股全市場回測
- A股UMP決策
- 美股全市場回測
- 美股UMP決策
abu量化系統文件教程持續更新中,請關注公眾號中的更新提醒。
更多關於量化交易相關請閱讀《量化交易之路》
更多關於量化交易與機器學習相關請閱讀《機器學習之路》
更多關於abu量化系統請關注微信公眾號: abu_quant