策略回测什么才是最重要的?速度?花里胡哨的绩效指标?答案是准确!是的,回测的目的是为了验证策略的逻辑和可行性。这也是回测本身的意义,其他都是次要的。一个能够真正反映策略在历史数据上的回测才具有参考价值,那些看似完美的回测曲线讲故事可以,实盘它不行。 点击阅读更多内容
/*backtest start: 2019-07-12 09:00:00 end: 2019-07-12 15:00:00 period: 1d exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}] mode: 1 */ enum State { STATE_NA, STATE_IDLE, STATE_HOLD_LONG, STATE_HOLD_SHORT, }; typedef struct { double bidPrice; double bidAmount; double askPrice; double askAmount; } Book; class HFT { public: State getState() { auto orders = exchange.GetOrders(); if (!orders.Valid || orders.size() == 2) { return STATE_NA; } bool foundCover = false; for (auto &order : orders) { if (order.Id == _coverId) { if ((order.Type == ORDER_TYPE_BUY && order.Price < _book.bidPrice - _slidePrice) || (order.Type == ORDER_TYPE_SELL && order.Price > _book.askPrice + _slidePrice)) { exchange.CancelOrder(order.Id, "Cancel Cover Order"); _countCancel++; _countRetry++; } else { foundCover = true; } } else { exchange.CancelOrder(order.Id); _countCancel++; } } if (foundCover) { return STATE_NA; } auto positions = exchange.GetPosition(); if (!positions.Valid) { return STATE_NA; } for (auto &pos : positions) { if (pos.ContractType == Symbol) { _holdPrice = pos.Price; _holdAmount = pos.Amount; _holdType = pos.Type; return pos.Type == PD_LONG || pos.Type == PD_LONG_YD ? STATE_HOLD_LONG : STATE_HOLD_SHORT; } } return STATE_IDLE; } void stop() { Log(exchange.GetOrders()); Log(exchange.GetPosition()); Log("Stop"); } bool Loop() { if (exchange.IO("status") == 0) { LogStatus(_D(), "Server not connect ...."); Sleep(1000); return true; } if (_ct.is_null()) { Log(_D(), "subscribe", Symbol); _ct = exchange.SetContractType(Symbol); if (!_ct.is_null()) { auto obj = _ct["Commodity"]["CommodityTickSize"]; int volumeMultiple = 1; if (obj.is_null()) { obj = _ct["PriceTick"]; volumeMultiple = _ct["VolumeMultiple"]; _exchangeId = _ct["ExchangeID"]; } else { volumeMultiple = _ct["Commodity"]["ContractSize"]; _exchangeId = _ct["Commodity"]["ExchangeNo"]; } if (obj.is_null() || obj <= 0) { Panic("PriceTick not found"); } if (_priceTick < 1) { exchange.SetPrecision(1, 0); } _priceTick = double(obj); _slidePrice = _priceTick * ProfitTick; _ins = _ct["InstrumentID"]; Log(_ins, _exchangeId, "PriceTick:", _priceTick, "VolumeMultiple:", volumeMultiple); } Sleep(1000); return true; } // Check orders and positions to set state auto depth = exchange.GetDepth(); if (!depth.Valid) { LogStatus(_D(), "Market not ready"); Sleep(1000); return true; } _countTick++; // _book not init if (_book.bidAmount == 0) { return true; } _preBook = _book; _book.bidPrice = depth.Bids[0].Price; _book.bidAmount = depth.Bids[0].Amount; _book.askPrice = depth.Asks[0].Price; _book.askAmount = depth.Asks[0].Amount; double weight = depth.Asks[0].Price + depth.Bids[0].Price; _priceHis.push_back(weight); if (_priceHis.size() > 100) { _priceHis.erase(_priceHis.begin()); } auto st = getState(); LogStatus(_D(), _ins, "State:", st, "Ask:", depth.Asks[0].Price, depth.Asks[0].Amount, "Bid:", depth.Bids[0].Price, depth.Bids[0].Amount, "Cancel:", _countCancel, "Tick:", _countTick); Log(depth.Asks[0], depth.Bids[0]); bool forceCover = _countRetry >= _retryMax; if (st == STATE_IDLE) { if (_holdAmount > 0) { if (_countRetry > 0) { _countLoss++; } else { _countWin++; } auto account = exchange.GetAccount(); if (account.Valid) { LogProfit(_N(account.Balance+account.FrozenBalance, 2), "Win:", _countWin, "Loss:", _countLoss); } } _countRetry = 0; _holdAmount = 0; if (_countCancel > _cancelMax) { Log("Cancel Exceed", _countCancel); return false; } if (IsVirtual()) { if (_priceHis.size() < _period) { LogStatus("calc history", _priceHis.size()); return true; } auto arr = talib.STDDEV(_priceHis, _period); double diff = abs(weight - _priceHis[_priceHis.size() - 2]); if (diff < arr[arr.size()-1]) { return true; } } else { bool canDo = false; if (_book.bidPrice == _preBook.bidPrice && _book.bidAmount > 50 && abs(_preBook.bidAmount-_book.bidAmount) > 50) { canDo = true; Log("Bid Amount", _preBook.bidAmount, "->", _book.bidAmount); } if (_book.askPrice == _preBook.askPrice && _book.askAmount > 50 && abs(_preBook.askAmount-_book.askAmount) > 50) { canDo = true; Log("Ask Amount", _preBook.askAmount, "->", _book.askAmount); } if (!canDo) { return true; } } exchange.SetDirection("buy"); exchange.Buy(depth.Bids[0].Price, 1); exchange.SetDirection("sell"); exchange.Sell(depth.Asks[0].Price, 1); } else if (st == STATE_HOLD_LONG) { exchange.SetDirection((_holdType == PD_LONG && _exchangeId == "SHFE") ? "closebuy_today" : "closebuy"); auto sellPrice = depth.Asks[0].Price; if (sellPrice > _holdPrice) { Log(_holdPrice, "Hit #ff0000"); //sellPrice = depth.Asks[0].Price + _slidePrice; } else if (sellPrice < _holdPrice) { //forceCover = true; } if (forceCover) { Log("StopLoss"); } _coverId = exchange.Sell(forceCover ? depth.Bids[0].Price : sellPrice, _holdAmount); if (!_coverId.Valid) { return false; } } else if (st == STATE_HOLD_SHORT) { exchange.SetDirection((_holdType == PD_SHORT && _exchangeId == "SHFE") ? "closesell_today" : "closesell"); auto buyPrice = depth.Bids[0].Price; if (buyPrice < _holdPrice) { Log(_holdPrice, "Hit #ff0000"); } else if (buyPrice > _holdPrice) { //forceCover = true; } if (forceCover) { Log("StopLoss"); } _coverId = exchange.Buy(forceCover ? depth.Asks[0].Price : buyPrice, _holdAmount); if (!_coverId.Valid) { return false; } } return true; } private: double _holdPrice = 0; double _holdAmount = 0; int _holdType; int _countTick = 0; int _countRetry = 0; int _countCancel = 0; int _period = 20; const int _retryMax = RetryMax; const int _cancelMax = 300; int _countLoss = 0; int _countWin = 0; json _ct; string _ins; string _exchangeId; double _priceTick; double _slidePrice; vector<double> _priceHis; Book _book; Book _preBook; TId _coverId; }; void main() { LogReset(); SetErrorFilter("ready|timeout"); Log("Init OK"); HFT hft; while (hft.Loop()); Log("Exit"); }