本系列文章將會介紹如何使用DolphinDB進行交易回測。本文以移動平均線指標爲例,介紹如何在DolphinDB中實現技術信號回測。移動平均線指標(Moving average,簡稱MA)屬於趨勢指標。在金融分析領域,移動平均線是不可缺乏的指標工具。除了指示趨勢,均線指標還能避免因爲股價下跌錯失清倉的機會,減小收益的損失,及時止損,也能避免股價上漲錯失買入的實際,從而得到更高的收益。html
回測過程當中,咱們考慮兩種狀況:不止損回測和止損回測。算法
數據表須要包含如下字段:數據庫
股票代碼:sym編程
日期:dateapp
收盤價格:close編程語言
當短時間均線大於長期均線時,咱們認爲這是一個MA交易信號。分佈式
def maSignal(x, shortHorizon, longHorizon){ signal = mavg(x, shortHorizon) > mavg(x, longHorizon) signal[0:min(x.size(), longHorizon - 1)] = NULL return signal }
咱們定義的交易算法以下:ide
假設前一天的MA信號爲prevSignal,當天的MA信號爲signal。函數
(1)若是prevSignal=false,signal=true,那麼買入多頭頭寸(long position)。工具
(2)若是prevSignal=true,signal=false,那麼賣出空頭頭寸(short position)。
(3)若是不符合以上兩種狀況,則保持與前一天相同的頭寸。
def backtest(t){ t2 = select sym,date,close,prev(close) as prevClose,signal, prev(signal) as prevSignal from t context by sym update t2 set position=iif(prevSignal==false and signal==true, 1 ,iif(prevSignal==true and signal==false, -1, int())).prev().ffill() context by sym return select sym,date,close,signal,position,position*(close - prevClose) as pnl from t2 where isValid(position) }
DolphinDB函數說明:
iif(condition, trueResult, falseResult):若是知足條件condition,則返回trueResult,不然返回falseResult。它至關於if...else語句,可是語法上更加簡潔。
int():返回int類型的NULL值。
prev(x):把向量中的全部元素向右移動一個位置。
ffill(x):使用NULL值前的非NULL元素填充向量中的NULL值。
isValid():檢查每一個元素是否爲NULL。若是爲NULL,返回0,不然返回1。
backtest 函數說明:
回測時首先整理數據,使用prev()函數把前一天的收盤價格prevClose和前一天的MA信號prevSignal與當天的數據對齊,便於計算。
接着,按照咱們定義的交易算法,計算每一個股票的頭寸position。position=1表示買入,position=-1表示賣出,position=NULL表示保持不變。
最後,使用position*(close - prevClose)計算盈虧pnl。
3.1 判斷止損點
首先,定義函數stoploss判斷是否須要止損。該函數返回布爾類型的向量。
def stoploss(ret, threshold){ cumret = cumprod(1+ret) drawDown = 1 - cumret / cumret.cummax() firstCutIndex = at(drawDown >= threshold).first() + 1 indicator = take(false, ret.size()) if(isValid(firstCutIndex) and firstCutIndex < ret.size()) indicator[firstCutIndex:] = true return indicator }
DolphinDB內置函數說明:
cumprod:計算累計乘積。
cummax:計算累計最大值。
at(x):x是布爾表達式,找出符合條件x的元素的位置。
first:返回第一個元素。
take(X, k):返回包含k個x的向量。
stoploss 函數說明:
首先計算累計回報率cumret,接着計算當前回報率和累計最大回報率的回撤drawdown,當回撤drawdown大於等於預設閾值threshold時,則認爲應當止損,並記錄止損的起始位置firstCutIndex(因爲到股市收盤時才知道是否須要止損或止盈,因此firstCutIndex要加1)。止損信號indicator的全部元素一開始設定爲全是false。若是止損的起始位置firstCutIndex不爲NULL,且不超過當前的數據量,則把止損信號indicator中從firstCutIndex開始到最後的全部元素設爲true,表示從firstCutIndex開始,都應當止損。
3.2 止損回測
回測時,將止損先後的盈虧進行對比 。
def backtest_stoploss(t, thresholdDrawDown){ t2 = select sym,date,close,prev(close) as prevClose,signal, prev(signal) as prevSignal from t context by sym update t2 set position=iif(prevSignal==false and signal==true, 1 ,iif(prevSignal==true and signal==false, -1, int())).prev().ffill() context by sym update t2 set pnl = position*(close - prevClose), ret = (close - prevClose)/prevClose update t2 set stoplossInd = segmentby(stoploss{,thresholdDrawDown}, ret, position) context by sym return select sym,date,close,signal,position,stoplossInd,pnl * stoplossInd as pnl, pnl as nostoplossPnl from t2 where isValid(position) }
DolphinDB函數說明:
segmentby(func, funcArgs, segment):把funcArgs分紅多個組,並把函數func應用到每一個組中。segment是一個向量,能夠把它看做是分組方案,連續相同的元素爲一組。經過下面的例子咱們能夠更好地理解segmentby:
x=1 2 3 0 3 2 1 4 5 y=1 1 1 -1 -1 -1 1 1 1 segmentby(cumsum,x,y) 1 3 6 0 3 5 1 5 10
上面的例子中,y定義了3個分組:1 1 一、-1 -1 -1 和1 1 1,第一個分組的index是0-2,第二個分組的index是3-5,第三個分組的index是6-9。按照這個規則把x分紅3組:1 2 三、0 3 二、1 4 5,並在每一個分組中計算累計和。
stoploss{, thresholdDrawDown}這種表達方式是定義一個部分應用,用於固定stoploss的第二個參數thresholdDrawDown。
backtest_stoploss 函數說明:
前三行代碼和1.2大體相同,除了計算盈虧pnl以外,還計算了回報率ret,由於stoploss函數須要ret做爲輸入。接着把每一個股票的回報率ret按階段分組(position中的元素連續多個1表示持續買入,連續多個-1表示持續賣出,連續多個NULL表示持續不變),在每一個階段分組中判斷是否須要止損,爲每隻股票生成止損信號stoplossInd。最後計算止損先後的盈虧,止損前的盈虧爲nostoplossPnl,止損後的盈虧爲pnl。
一般狀況下,咱們還須要分析盈虧的統計信息。經過下面的自定義函數calcPerformance能夠計算盈虧的統計信息,好比累計盈虧cumpnl、平均盈虧avgpnl、盈虧天數days、盈虧的標準差std、最大回撤maxDrawdown等。返回的數據類型是字典。
def calcPerformance(pnl){ result = dict(STRING, DOUBLE) result[`cumpnl]= pnl.sum() result[`avgpnl]= pnl.avg() result[`days] = pnl.size() result[`std]= pnl.std() result[`maxDrawdown] = (pnl.cumsum().cummax() - pnl.cumsum()).max() return result }
咱們使用美國股市從1998年到2016年股票的每日交易信息做爲數據集來進行測試。數據集共包含3474萬條記錄。
//數據導入和數據處理,產生stock數據表,包含sym, date, close三個字段 ... //計算每一個股票天天的MA信號 t = select sym,date,close,maSignal(close, 50, 100) as signal from stock context by sym
狀況一:不止損回測
//不止損回測 positions = backtest(t) //計算盈虧並繪製盈虧走勢圖 dailyPnl = select sum(pnl) as pnl from positions group by date order by date calcPerformance(dailyPnl.pnl) plot(dailyPnl.pnl.cumsum() as cumulativePnl, dailyPnl.date, "Cumulative Pnl of All Stocks without Stop Loss Control") //分析每隻股票的盈虧信息 select calcPerformance(pnl) as `cumpnl`avgpnl`days`std`maxDrawdown from result group by sym sym cumpnl avgpnl days std maxDrawdown A 48.75 0.0108 4,513. 1.5895 106.55 AA 7.9625 0.0017 4,624. 1.131 119.75 ...
不止損回測全部股票的盈虧走勢圖
狀況二:止損回測。咱們把預設閾值設爲2.5%。
//止損回測 positions = backtst_stoploss(t,0.025) //計算盈虧並繪製盈虧走勢圖 dailyPnl = select sum(pnl) as pnl from positions group by date order by date calcPerformance(dailyPnl.pnl) plot(dailyPnl.pnl.cumsum() as cumulativePnl, dailyPnl.date, "Cumulative Pnl of All Stocks with Stop Loss Control") //分析每隻股票的盈虧信息 select calcPerformance(pnl) as `cumpnl`avgpnl`days`std`maxDrawdown from result group by sym sym cumpnl avgpnl days std maxDrawdown A 58.2775 0.0129 4,513. 1.5731 102.125 AA 20.47 0.0044 4,624. 1.1126 110.8125 ...
止損回測全部股票的盈虧走勢圖
DolphinDB database 雖然是一個通用的分佈式時序數據庫,但由於內置極其高效的多範式編程語言,開發效率很是高。若是回測不用考慮止損,僅用了3行代碼計算MA信號,3行代碼進行回測。DolphinDB的運行效率更是驚人,對美國股市18年的所有股票按日進行回測,不止損回測執行耗時僅4秒多,止損回測僅7秒多。
本文的目的是從技術上幫助金融工程師使用DolphinDB快速實現交易回測。文中採用的各類參數,譬如長短線時間,止損閾值,數據過濾的方法等等,只是起到演示的做用,並不是實踐中的最佳參數。
歡迎訪問官網下載DolphinDB試用版