天天看點

Dresdon二次開發

  在上一篇文章中,我們已經對Dresdon所提供的功能進行了簡單的介紹。在這篇文章中,我們将介紹如何基于Dresdon進行二次開發。

Dresdon的擴充點

  就像上一篇文章所介紹的那樣,Dresdon主要是一個量化引擎。使用者可以通過腳本或者Java程式設計的方式來描述模型的買賣條件,并進一步通過掃描該模型在所有股票中的所有比對來評估該模型的具體表現。通過這種方式,使用者可以很大程度地優化自己的交易系統,進而實作穩定盈利。

  通過腳本來描述股票的買入賣出條件十分簡單:

// 當日和前日股價上漲
$isRaisingUp = growth(close()) > 0.01 && growth(close(), 1) > 0.01

// 5日前存在着一個長度至少為30,震蕩幅度小于5%的平台
$isPlatform = platform_exists(0.05, 30, 5)

// 在平台前存在長度至少為20日,最大上漲幅度為12%的緩慢增長
$isSlowRaiseBeforePlatform = is_slow_raise(0.12, 20, platform_start(0.05, 30, 5))
……

$buyCondition = $isRaisingUp && $isPlatform && $isSlowRaiseBeforePlatform && ……
$sellCondition = close(0) < ma5(0) && ……      

  接下裡使用者就可以通過掃描2006年1月到2020年4月之間所有比對來統計該模型的表現:

{
    "averageBenefit" : 0.049035519980520418, // 平均單筆收益為4.9%左右
    "maxBenefit" : 74.86122362293308, // 最高收益為74.9%
    "minBenefit" : -4.000000000000014, // 最大止損為4%
    "totalCount" : 313, // 2006.01 – 2020.04之間比對313次
    "averageDayCount" : 11.875656742556918, // 平均持股時間為11.9天
    "successRatio" : 0.46059544658493873 // 成功率為46%左右
}      

  當然,如果使用者會Java,那麼他還可以将模型寫成一個Java類,進而得到編譯器的強類型支援:

// 當日和前日股價上漲
BooleanHolder isRaisingUp = and(greaterThan(growth(close()), 0), greaterThan(growth(close(), 1), 0));

// 5日前存在着一個長度至少為30,震蕩幅度小于5%的平台
BooleanHolder platformExists = platformExists(0.05, 30, 5)

// 在平台前存在長度至少為20日,最大上漲幅度為12%的緩慢增長
IntHolder platformStart = platformStart(0.05, 30, 5);
BooleanHolder isSlowRaiseBeforePlatform = isSlowRaise(0.12, 20, platformStart);
……

BooleanHolder condition = and(isRaisingUp, platformExists, isSlowRaiseBeforePlatform, ……);      

  除了添加自定義模型之外,使用者還可以添加自定義函數。這些函數可以用來判斷某日K線的特征,或者拟合特定K線形态。例如下面就是一個用來計算指定K線震動幅度的函數:

public static Value.Shrink shrink(int index) {
    return new Value.Shrink(index);
}

@Operation(key = KEY_SHRINK, resultType = DOUBLE, arguments = {
        @Arguments(paramTypes = { INT })
})
public static class Shrink extends HolderBase<Double> implements DoubleHolder {
    protected IntHolder index;

    protected Integer indexValue;

    public Shrink(int index) {
        this(new IntValue(index));
    }

    public Shrink(IntHolder index) {
        super(KEY_SHRINK); 

        this.index = index;
    }

    @Override
    public void preprocess(QuantContext context) {
        super.preprocess(context);

        preprocess(context, index);
    }

    @Override
    public boolean needRefresh(QuantContext context) {
        return !equals(indexValue, index, context);
    }

    @Override
    protected Double recalculate(QuantContext context) {
        indexValue = index.getValue(context);
        if (indexValue == null) {
            return null;
        }

        DailyTrading dailyTrading = context.getTradings().get(indexValue);
        double blockSize = Math.abs(dailyTrading.getClose() - dailyTrading.getOpen());
        double totalVariation = dailyTrading.getHigh() - dailyTrading.getLow();
        this.value = blockSize > SMALL_DOUBLE_VALUE ? totalVariation / blockSize : Double.MAX_VALUE;
        return value;
    }

    @Override
    public void persist(StringBuilder builder) {
        builder.append(getKey());
        builder.append(INDEX_START);
        getIndex().persist(builder);
        builder.append(INDEX_END);
    }
}      

  在後面的章節中,我們将詳細講解上面模型中各個買入賣出條件的意義。

添加自定義模型

  下面就讓我們從添加自定義模型開始。抽取一個模型常常需要經過以下一系列步驟:

  1. 确定模型形态。使用者首先需要确定需要比對的模型的大緻形态有哪些,如起漲階段的線形是什麼樣子的,整理期是以什麼形态呈現的,甚至之前籌碼是如何收集的等等。

  2. 初篩并收集目标比對。使用者需要為該模型定義一個大緻的比對條件,然後運作引擎。此時得到的結果可能存在着大量的噪音,是以統計資料常常并不好看。但其中也會包含大量的具有較高準确度的比對。而這些比對常常是模型的目标比對。

  3. 細化模型。添加其它條件逐漸祛除噪音,以提高模型正确比對的比率。

  4. 細化賣出條件。添加其它賣出送出,以提高模型的收益率及成功率。

  當然,凡事都有一個從陌生到熟悉的過程。在添加了幾個模型之後,使用者可能就能摸到其中的訣竅,進而大大提高模型抽取的效率。在這裡給大家列出來我在抽取模型過程中最常使用的一系列經驗型政策,避免大家重走我之前的彎路。

  首先,模型的買入特征線型要明顯,近端的輔助判斷邏輯要嚴格,而遠端的輔助判斷邏輯要具有較高的容錯性。可以說,所謂的股票拉升實際上就是股票價格的異動,而該異動的阻力則很大程度上決定了股票行情到底能走多遠。是以起漲階段線形的略微不同都可能導緻量化結果産生非常大的差異。比如都是上漲5%,一個有長上影的K線就遠不如沒有長上影的K線。反之離目前交易日越遠的交易,其對目前股價的影響越小,是以遠端的輔助判斷邏輯不宜非常嚴格。

  其次,要對常見線形所代表的意義有正确的了解。同樣的K線在不同的位置其意義常常并不相同。例如一般來說,低位揉搓線常常是一個好的K線組合,而高位揉搓線,尤其是放量揉搓線則很可能代表一段行情将要終結。

  最後,篩選條件常常是可以通用的。就像第一條所說的那樣,我們要将買入的特征線形嚴格地區分。比如拉升是通過一根陽線完成的,和拉升是通過三根K線形成的組合K線完成的效果類似。但是它們的篩選邏輯則常常有一個為2的索引差:一根陽線完成的拉升,我們要從前一天的K線檢查,而三根K線組成的拉升,則需要從三天前的交易開始檢查。隻不過這些檢查的參數有些不太相同而已。

 

添加自定義函數

  在編寫一段時間的模型之後,使用者可能就會感覺到引擎内建的各個表達式很難表現一些特定的限制條件。例如他可能常常需要通過如下表達式來限制K線的波動情況:

$noBigShrink = abs(close(0) – open(0)) * 5 < high(0) – low(0)      

  甚至用Java編寫出來的表達式的可讀性更差:

BooleanHolder noBigShrink = lessThan(multiply(abs(minus(close(0), open(0))), 5), minus(high(0), low(0)));      

  而這部分的邏輯僅僅是在判斷當日K線的實體是否過小,進而呈現十字星或錘頭線等形态。此時使用者就可以在Plugin裡面添加自定義的表達式:

public static Value.Shrink shrink(int index) {
    return new Value.Shrink(index);
}

@Operation(key = KEY_SHRINK, resultType = DOUBLE, arguments = {
        @Arguments(paramTypes = { INT })
})
public static class Shrink extends HolderBase<Double> implements DoubleHolder {
    protected IntHolder index;

    protected Integer indexValue;

    public Shrink(int index) {
        this(new IntValue(index));
    }

    public Shrink(IntHolder index) {
        super(KEY_SHRINK); 

        this.index = index;
    }

    @Override
    public void preprocess(QuantContext context) {
        super.preprocess(context);

        preprocess(context, index);
    }

    @Override
    public boolean needRefresh(QuantContext context) {
        return !equals(indexValue, index, context);
    }

    @Override
    protected Double recalculate(QuantContext context) {
        indexValue = index.getValue(context);
        if (indexValue == null) {
            return null;
        }

        DailyTrading dailyTrading = context.getTradings().get(indexValue);
        double blockSize = Math.abs(dailyTrading.getClose() - dailyTrading.getOpen());
        double totalVariation = dailyTrading.getHigh() - dailyTrading.getLow();
        this.value = blockSize > SMALL_DOUBLE_VALUE ? totalVariation / blockSize : Double.MAX_VALUE;
        return value;
    }

    @Override
    public void persist(StringBuilder builder) {
        builder.append(getKey());
        builder.append(INDEX_START);
        getIndex().persist(builder);
        builder.append(INDEX_END);
    }
}      

  下面就讓我們一行行地講解這些代碼的含義。首先是一個靜态函數:

public static Value.Shrink shrink(int index) {
    return new Value.Shrink(index);
}      

  通過該靜态函數,使用者可以更直覺地描述模型邏輯,屬于一種文法糖:

new lessThan(new Shrink(0), 5) vs. lessThan(shrink(0), 5)      

  接下來我們則通過@Operation來标明目前類中包含的邏輯是一個引擎操作的定義。該操作的key為KEY_SHRINK,帶有一個Integer類型的參數,傳回值的類型為Double:

@Operation(key = KEY_SHRINK, resultType = DOUBLE, arguments = {
        @Arguments(paramTypes = { INT })
})
public static class Shrink extends HolderBase<Double> implements DoubleHolder {      

  這裡有一個概念,那就是Holder。馬上您就會看到,Shrink類執行個體上并沒有記錄和交易相關的資料,它僅僅用來承載計算邏輯。也就是說,它相當于一個占位符。實際上,Dresdon支援的所有運算符都是一個Holder,内部隻記錄算法,不記錄任何資料。

  那麼交易相關的資料都記錄在哪裡呢?答案是Context。使用者可以通過各個holder的getValue()函數來得到各個holder的目前值。現在就讓我們看看Shrink類的recalculate()函數的是如何使用它的:

@Override
protected Double recalculate(QuantContext context) {
    indexValue = index.getValue(context);
    if (indexValue == null) {
        return null;
    }

    DailyTrading dailyTrading = context.getTradings().get(indexValue);
    double blockSize = Math.abs(dailyTrading.getClose() - dailyTrading.getOpen());
    double totalVariation = dailyTrading.getHigh() - dailyTrading.getLow();
    this.value = blockSize > SMALL_DOUBLE_VALUE ? totalVariation / blockSize : Double.MAX_VALUE;
    return value;
}      

  可以看到,recalculate()函數傳入了一個QuantContext類型的執行個體。接下來,該函數的實作通過調用index的getValue()函數得到了index的實際值。接下來,我們就從context中取得了目标交易資料(dailyTrading),并依次通過計算目标K線的實體大小(blockSize),當日最高價和最低價之差(totalVariation)來計算當日的波動情況。這裡需要注意的是,計算結果将被首先記錄在value這個域中,然後才被該函數傳回。

  為了提高計算的性能,我們引入了兩個機制:refresh和preprocess。前者通過判斷參數的值是否變化來确定是否需要運作recalculate()函數。畢竟該函數所包含的計算邏輯可能相當複雜。在其它屬性沒有發生變化的時候,我們可以通過直接傳回value這個緩存域中記錄的值來提高運作性能:

@Override
public boolean needRefresh(QuantContext context) {
    return !equals(indexValue, index, context);
}      

  另一種情況則是對預處理的支援。其主要用來提高拟合功能的性能。讓我們以一隻股票在多年的交易中存在着一系列盤整平台的情況為例。如果我們針對不同的日期都計算一次拟合邏輯,那麼引擎的性能将變得很差。畢竟在平台内部的各個交易日對應的是同一個平台。為了解決這個問題,我們添加了預處理步驟。該步驟允許引擎對所有交易日進行一次掃描,并将其掃描結果存儲在Context中。在需要的時候從Context中取出相應的預處理結果即可:

@Override
public void preprocess(QuantContext context) {
    ……
    PlatformExtractor extractor = new PlatformExtractor(symbol, ma5s, rangeValue, minLengthValue);
    List<PlatformInfo> platforms = extractor.extractPlatforms();
    context.getVariableMap().setVariable(key, new ObjectWrapper(symbol, platforms));
}

protected PlatformInfo getCurrentPlatform(QuantContext context) {
    ……
    ValueHolder<?> variable = context.getVariableMap().getVariable(key);
    List<PlatformInfo> platforms = (List<PlatformInfo>)(((ObjectWrapper)variable).getValue());
    return platforms.stream().filter(platform -> platform.getStartDate().compareTo(seedDate) < 0
             && platform.getEndDate().compareTo(seedDate) > 0).findFirst().orElse(null);
}      

  通過這種方法,使用者就可以自行建立更進階的函數,進而使得自己的模型變得更為簡潔。

轉載請注明原文位址并标明轉載:https://www.cnblogs.com/loveis715/p/13324937.html

商業轉載請事先與我聯系:[email protected]

公衆号一定幫忙别标成原創,因為協調起來太麻煩了。。。