I. Summary

Benjamin Graham, Warren Buffett's mentor, has mentioned a dynamic balance trading mode of stocks and bonds in the book "The Intelligent Investor".

/upload/asset/28dc23186fd164b8f84bd.png

The trading mode is very simple:
-Invest 50% of the funds in stock funds and the remaining 50% in bond funds. That is, stocks and bonds account for half of each other.
-According to the fixed interval or market changes, carry out an asset rebalancing to restore the proportion of stock assets and bond assets to the original 1:1.
This is the logic of the whole strategy, including when to buy and sell and how much to buy and sell. It's easy enough!

II. Dynamic balance principle

In this method, the volatility of bond funds is very small actually, far lower than the volatility of stocks, so bonds are used as "reference anchors" here, that is, to measure whether stocks have risen too much or too little by bonds.
If the stock price rises, the market value of the stock will be greater than the market value of the bonds. When the market value ratio of the two exceeds the threshold set, the total position will be readjusted, the stocks will be sold, and the bonds will be purchased, so that the market value ratio of the stocks and bonds will return to the original 1:1.
On the contrary, if the stock price decreases, the market value of the stock will be smaller than the market value of the bonds. When the market value ratio of the two exceeds the threshold set, the total position will be readjusted, stocks will be bought, and bonds will be sold, so that the market value ratio of stocks and bonds will return to the original 1:1.
In this way, we can enjoy the fruits of stock growth and reduce asset volatility by balancing the proportion between stocks and bonds dynamically. As a pioneer of value investment, Graham has provided us with a good idea. Since this is a complete strategy, why don't we use it in the digital currency?

III. Strategy logic

Dynamic balance strategy in blockchain asset BTC

Strategy logic

  • According to the current BTC value, the account balance reserves a cash of ¥5000 and 0.1 BTC, that is, the initial ratio of cash to BTC market value is 1:1.
  • If the price of BTC increases to ¥6000, that is, the market value of BTC is greater than the account balance, and the difference between them exceeds the threshold set, then sell (6000-5000)/6000/2 coins. It means that the BTC has appreciated and we can exchange the money back.
  • If the price of BTC decreases to ¥4000, that is, the market value of BTC is less than the account balance, and the difference between them exceeds the threshold set, then buy (5000-4000)/4000/2 coins. It means that the BTC has depreciated, and we can buy BTC back.

In this way, no matter whether BTC appreciates or depreciates, we always keep the account balance and BTC's market value equal dynamically. If the BTC depreciates, we buy, and if it rises again, we sell some, just like a balance.

IV. Strategy framework

So, how to implement it in code? We take the FMZ Quant Trading Platform as an example, let's take a look at the strategy framework first:

// function to cancel orders
function CancelPendingOrders() {}

// function to place an order
function onTick() {}

// main function
function main() {
    // filter non-important information
    SetErrorFilter("GetRecords:|GetOrders:|GetDepth:|GetAccount|:Buy|Sell|timeout");
    while (true) { // polling mode
        if (onTick()) { // execute onTick function
            CancelPendingOrders(); // cancel the outstanding pending orders
            Log(_C(exchange.GetAccount)); // print the current account information
        }
        Sleep(LoopInterval * 1000); // sleep
    }
}

The entire strategy framework is very simple actually, including a main function, an onTick order-placing function, a CancelPendingOrders function, and the necessary parameters.

V. Order placement module

// order-placing function
function onTick() {
    var acc = _C(exchange.GetAccount); // obtain account information
    var ticker = _C(exchange.GetTicker); // obtain Tick data
    var spread = ticker.Sell - ticker.Buy; // obtain bid ask spread of Tick data
    // 0.5 times of the difference between the account balance and the current position value
    var diffAsset = (acc.Balance - (acc.Stocks * ticker.Sell)) / 2;
    var ratio = diffAsset / acc.Balance; // diffAsset / account balance
    LogStatus('ratio:', ratio, _D()); // Print ratio and current time
    if (Math.abs(ratio) < threshold) { // If the absolute value of the ratio is less than the specified threshold
        return false; // return false
    }
    if (ratio > 0) { // if ratio > 0
        var buyPrice = _N(ticker.Sell + spread, ZPrecision); // Calculate the price of an order
        var buyAmount = _N(diffAsset / buyPrice, XPrecision); // Calculate the order quantity
        if (buyAmount < MinStock) { // If the order quantity is less than the minimum transaction quantity
            return false; // return false
        }
        exchange.Buy(buyPrice, buyAmount, diffAsset, ratio); // Purchase order
    } else {
        var sellPrice = _N(ticker.Buy - spread, ZPrecision); // Calculate the price of an order
        var sellAmount = _N(-diffAsset / sellPrice, XPrecision); // Calculate the order quantity
        if (sellAmount < MinStock) { // If the order quantity is less than the minimum transaction quantity
            return false; // return false
        }
        exchange.Sell(sellPrice, sellAmount, diffAsset, ratio); // Sell and place an order
    }
    return true; // return true
}

The order trading logic is well organized, and all comments have been written into the code. You can click the image to zoom in.

The main process is as follows:

  • Get account information.
  • Get the Tick data.
  • Calculate the bid ask spread of Tick data.
  • Calculate the difference between account balance and BTC market value.
  • Calculate the purchase and sell conditions, order price and order quantity.
  • Place an order and return true.

VI. Withdrawal module

// Withdrawal function
function CancelPendingOrders() {
    Sleep(1000); // Sleep for 1 second
    var ret = false;
    while (true) {
        var orders = null;
        // Obtain the unsettled order array continuously. If an exception is returned, continue to obtain
        while (!(orders = exchange.GetOrders())) {
            Sleep(1000); // Sleep for 1 second
        }
        if (orders.length == 0) { // If the order array is empty
            return ret; // Return to order withdrawal status
        }
        for (var j = 0; j < orders.length; j++) { // Iterate through the array of unfilled orders
            exchange.CancelOrder(orders[j].Id); // Cancel unfilled orders in sequence
            ret = true;
            if (j < (orders.length - 1)) {
                Sleep(1000); // Sleep for 1 second
            }
        }
    }
}

The withdrawal module is simpler. The steps are as follows:

  • Wait for 1 second before canceling the order. For some exchanges, you know what I mean.
  • Continuously obtain the unsettled order array. If an exception is returned, continue to obtain.
  • If the unsettled orders array is empty, the withdrawal status will be returned immediately.
  • If there is an unsettled order, the whole array is traversed and the order is cancelled according to the order number.

VII. Complete strategy source code

// Backtest environment
/*backtest
start: 2018-01-01 00:00:00
end: 2018-08-01 11:00:00
period: 1m
exchanges: [{"eid":"Bitfinex","currency":"BTC_USD"}]
*/



// Order withdrawal function
function CancelPendingOrders() {
    Sleep(1000); // Sleep for 1 second
    var ret = false;
    while (true) {
        var orders = null;
        // Obtain the unsettled order array continuously. If an exception is returned, continue to obtain
        while (!(orders = exchange.GetOrders())) {
            Sleep(1000); // Sleep for 1 second
        }
        if (orders.length == 0) { // If the order array is empty
            return ret; // Return to order withdrawal status
        }
        for (var j = 0; j < orders.length; j++) { // Iterate through the array of unfilled orders
            exchange.CancelOrder(orders[j].Id); // Cancel unfilled orders in sequence
            ret = true;
            if (j < (orders.length - 1)) {
                Sleep(1000); // Sleep for 1 second
            }
        }
    }
}

// Order function
function onTick() {
    var acc = _C(exchange.GetAccount); // obtain account information
    var ticker = _C(exchange.GetTicker); // obtain Tick data
    var spread = ticker.Sell - ticker.Buy; // obtain bid ask spread of Tick data
    // 0.5 times of the difference between the account balance and the current position value
    var diffAsset = (acc.Balance - (acc.Stocks * ticker.Sell)) / 2;
    var ratio = diffAsset / acc.Balance; // diffAsset / account balance
    LogStatus('ratio:', ratio, _D()); // Print ratio and current time
    if (Math.abs(ratio) < threshold) { // If the absolute value of ratio is less than the specified threshold
        return false; // return false
    }
    if (ratio > 0) { // if ratio > 0
        var buyPrice = _N(ticker.Sell + spread, ZPrecision); // Calculate the order price
        var buyAmount = _N(diffAsset / buyPrice, XPrecision); // Calculate the order quantity
        if (buyAmount < MinStock) { // If the order quantity is less than the minimum trading quantity
            return false; // return false
        }
        exchange.Buy(buyPrice, buyAmount, diffAsset, ratio); // buy order
    } else {
        var sellPrice = _N(ticker.Buy - spread, ZPrecision); // Calculate the order price
        var sellAmount = _N(-diffAsset / sellPrice, XPrecision); // Calculate the order quantity
        if (sellAmount < MinStock) { // If the order quantity is less than the minimum trading quantity
            return false; // return false
        }
        exchange.Sell(sellPrice, sellAmount, diffAsset, ratio); // sell order
    }
    return true; // return true
}

// main function
function main() {
    // Filter non-important information
    SetErrorFilter("GetRecords:|GetOrders:|GetDepth:|GetAccount|:Buy|Sell|timeout");
    while (true) { // Polling mode
        if (onTick()) { // Execute onTick function
            CancelPendingOrders(); // Cancel pending orders
            Log(_C(exchange.GetAccount)); // Print current account information
        }
        Sleep(LoopInterval * 1000); // sleep
    }
}

External parameters

/upload/asset/28e1de468db9be33ed265.png

VIII. Strategy backtesting

Next, let's test this simple dynamic balancing strategy to see if it works. The following is a backtest on the historical data of BTC for reference only.

Backtesting environment
/upload/asset/28d6d2e1b5f1a9ede4d6b.png

Backtesting Performance
/upload/asset/28d8927c56f9aa9a46e8e.png

Backtesting curve
/upload/asset/28d945d245643ef8c8f63.png

During the backtest period, BTC has continued to decline for up to 8 months, even with a maximum decline of more than 70%, which caused many investors to lose confidence in blockchain assets. The cumulative return of this strategy is up to 160%, and the annualized return risk ratio exceeds 5. For such a simple investment strategy, the rate of return on investment has exceeded that of most people who are in a full position.

IX. Get the strategy source code

The strategy source code has been published on the FMZ Quant official website: https://www.fmz.com/strategy/110545. There is no need to configure, you can backtesting online directly.

X. Summary

The dynamic balance strategy in this article has only one core parameter (threshold), which is a very simple investment method. What it pursues is not excess return, but steady return. Contrary to the trend strategy, the dynamic balance strategy is against the trend. But the dynamic balance strategy is just the opposite. When the market is popular, reducing the position, while when the market is unpopular, scaling in the position, which is similar to macroeconomic regulation.

In fact, the dynamic balance strategy is a craft that inherits the concept of unpredictable prices and captures price fluctuations at the same time. The core of the dynamic balance strategy is to set and adjust the asset allocation ratio, as well as the trigger threshold. In view of the length, an article can not be comprehensive. You should know that beyond the words, there is a heart. The most important part of the dynamic balance strategy is the investment idea. You can even replace the individual BTC assets in this article with a basket of blockchain asset portfolios.

Finally, let's close this article with Benjamin Graham's famous words in the book "The Intelligent Investor": The stock market is not a "weighing machine" that can measure value accurately, but rather a "voting machine". The decisions made by countless people are a mixture of rationality and sensibility. Many times these decisions are far from rational value judgments. The secret of investment is to invest when the price is far lower than the intrinsic value, and believe that the market trend will rebound.
——Benjamin Graham "The Intelligent Investor"

Leave a Reply

Your email address will not be published. Required fields are marked *