Teach you to write strategies -- transplant a MyLanguage strategy
Recently, when talking about strategies with my friends, I learned that many strategies written in MyLanguage suffer from flexibility. In many cases, it is necessary to use the standard K-line period that is not provided by the system. For example, the maximum requirement is to use the K-line for 4 hours. This problem has been solved in an article. If you are interested, please take a look: Link. However, in MyLanguage strategy, due to the high encapsulation feature of MyLanguage, it is not flexible to process data by itself. At this time, it is necessary to transplant the strategy idea to other languages.
It is very simple for trend strategy transplant. We can use a sample code to fill in the data calculation part of the code that drives the strategy, and fill in the trading signal trigger conditions.
Reusable sample code:
Take the strategy for OKX futures as an example.
// Global variables
var IDLE = 0
var LONG = 1
var SHORT = 2
var OPENLONG = 3
var OPENSHORT = 4
var COVERLONG = 5
var COVERSHORT = 6
var BREAK = 9
var SHOCK = 10
var _State = IDLE
var Amount = 0 // Record the number of positions
var TradeInterval = 500 // Polling intervals
var PriceTick = 1 // Price per jump
var Symbol = "this_week"
function OnTick(){
// Ticker processing part of the driving strategy
// To be filled...
// Trading signal trigger processing section
// To be filled...
// Execution of trading logic
var pos = null
var price = null
var currBar = records[records.length - 1]
if(_State == OPENLONG){
pos = GetPosition(PD_LONG)
// Determine whether the state is satisfied, and if so, modify the state.
if(pos[1] >= Amount){
_State = LONG
Amount = pos[1] // Update the actual volume.
return
}
price = currBar.Close - (currBar.Close % PriceTick) + PriceTick * 2
Trade(OPENLONG, price, Amount - pos[1], pos, PriceTick) // (Type, Price, Amount, CurrPos, PriceTick)
}
if(_State == OPENSHORT){
pos = GetPosition(PD_SHORT)
if(pos[1] >= Amount){
_State = SHORT
Amount = pos[1] // Update the actual volume.
return
}
price = currBar.Close - (currBar.Close % PriceTick) - PriceTick * 2
Trade(OPENSHORT, price, Amount - pos[1], pos, PriceTick)
}
if(_State == COVERLONG){
pos = GetPosition(PD_LONG)
if(pos[1] == 0){
_State = IDLE
return
}
price = currBar.Close - (currBar.Close % PriceTick) - PriceTick * 2
Trade(COVERLONG, price, pos[1], pos, PriceTick)
}
if(_State == COVERSHORT){
pos = GetPosition(PD_SHORT)
if(pos[1] == 0){
_State = IDLE
return
}
price = currBar.Close - (currBar.Close % PriceTick) + PriceTick * 2
Trade(COVERSHORT, price, pos[1], pos, PriceTick)
}
}
// Trading logic section
function GetPosition(posType) {
var positions = _C(exchange.GetPosition)
var count = 0
for(var j = 0; j < positions.length; j++){
if(positions[j].ContractType == Symbol){
count++
}
}
if(count > 1){
throw "positions error:" + JSON.stringify(positions)
}
for (var i = 0; i < positions.length; i++) {
if (positions[i].ContractType == Symbol && positions[i].Type === posType) {
return [positions[i].Price, positions[i].Amount];
}
}
Sleep(TradeInterval);
return [0, 0];
}
function CancelPendingOrders() {
while (true) {
var orders = _C(exchange.GetOrders)
for (var i = 0; i < orders.length; i++) {
exchange.CancelOrder(orders[i].Id);
Sleep(TradeInterval);
}
if (orders.length === 0) {
break;
}
}
}
function Trade(Type, Price, Amount, CurrPos, OnePriceTick){ // Processing transactions
if(Type == OPENLONG || Type == OPENSHORT){ // Processing of opening positions
exchange.SetDirection(Type == OPENLONG ? "buy" : "sell")
var pfnOpen = Type == OPENLONG ? exchange.Buy : exchange.Sell
var idOpen = pfnOpen(Price, Amount, CurrPos, OnePriceTick, Type)
Sleep(TradeInterval)
if(idOpen) {
exchange.CancelOrder(idOpen)
} else {
CancelPendingOrders()
}
} else if(Type == COVERLONG || Type == COVERSHORT){ // Processing of closing positions
exchange.SetDirection(Type == COVERLONG ? "closebuy" : "closesell")
var pfnCover = Type == COVERLONG ? exchange.Sell : exchange.Buy
var idCover = pfnCover(Price, Amount, CurrPos, OnePriceTick, Type)
Sleep(TradeInterval)
if(idCover){
exchange.CancelOrder(idCover)
} else {
CancelPendingOrders()
}
} else {
throw "Type error:" + Type
}
}
function main() {
// Set up the contract
exchange.SetContractType(Symbol)
while(1){
OnTick()
Sleep(1000)
}
}
Example: Transplantation of double EMA strategy
MyLanguage backtest:
MyLanguage strategy code:
MA5^^MA(C,5);
MA15^^MA(C,15);
CROSSUP(MA5,MA15),BPK;
CROSSDOWN(MA5,MA15),SPK;
Transplant to JavaScript Strategy
First, fill in the ticker acquisition and indicator calculation parts for the reusable sample code:
// The ticker processing part of the driving strategy
var records = _C(exchange.GetRecords)
if (records.length < 15) {
return
}
var ma5 = TA.MA(records, 5)
var ma15 = TA.MA(records, 15)
var ma5_pre = ma5[ma5.length - 3]
var ma15_pre = ma15[ma15.length - 3]
var ma5_curr = ma5[ma5.length - 2]
var ma15_curr = ma15[ma15.length - 2]
As you can see, the double EMA strategy is very simple. First, obtain the K-line data records
, and then use the EMA function TA.MA
of TA function library
to calculate the 5-day EMA and the 15-day EMA (as we can see in the backtest interface, the K-line period is set to the daily K-line, so TA.MA(records, 5)
is to calculate the 5-day EMA, TA.MA(records, 15)
is to calculate the 15-day EMA).
Then get the penultimate point ma5_curr
(indicator value), the last third point ma5_pre
(indicator value) of the indicator data ma5
, and the same for the ma15
indicator data. Then we can use these indicator data to judge the Golden Cross and Bearish Crossover, as shown in the figure:
Whenever such a state is formed, it is a definite Golden Cross or Bearish Crossover.
Then the part of judging the signal can be written as follows:
if(_State == IDLE && ma5_pre < ma15_pre && ma5_curr > ma15_curr){
_State = OPENLONG
Amount = 1
}
if(_State == IDLE && ma5_pre > ma15_pre && ma5_curr < ma15_curr){
_State = OPENSHORT
Amount = 1
}
if(_State == LONG && ma5_pre > ma15_pre && ma5_curr < ma15_curr){
_State = COVERLONG
Amount = 1
}
if(_State == SHORT && ma5_pre < ma15_pre && ma5_curr > ma15_curr){
_State = COVERSHORT
Amount = 1
}
In this way, the transplant is OK. We can backtest:
Backtesting of JavaScript strategy
Backtesting configuration:
Backtesting result:
Backtesting of MyLanguage
It can be seen that the results of the backtest are almost the same. In this way, if you want to continue to add interactive functions, data processing (such as K-line synthesis), and customized chart display to the strategy, you can achieve this.
If you are interested, please try.
Subscribe to my newsletter
Read articles from FMZ Quant directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
FMZ Quant
FMZ Quant
Quantitative Trading For Everyone