在上一篇文章中,咱們已經對Dresdon所提供的功能進行了簡單的介紹。在這篇文章中,咱們將介紹如何基於Dresdon進行二次開發。html
就像上一篇文章所介紹的那樣,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類,進而獲得編譯器的強類型支持:app
// 當日和前日股價上漲 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線震動幅度的函數:less
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); } }
在後面的章節中,咱們將詳細講解上面模型中各個買入賣出條件的意義。ide
下面就讓咱們從添加自定義模型開始。抽取一個模型經常須要通過如下一系列步驟:函數
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); }
經過這種方法,用戶就能夠自行建立更高級的函數,進而使得本身的模型變得更爲簡潔。
轉載請註明原文地址並標明轉載:http://www.javashuo.com/article/p-yettoyyd-mv.html
商業轉載請事先與我聯繫:silverfox715@sina.com
公衆號必定幫忙別標成原創,由於協調起來太麻煩了。。。