
O objetivo deste artigo é compartilhar algumas experiências e dicas no desenvolvimento de estratégias, o que pode permitir que os leitores compreendam rapidamente a experiência no desenvolvimento de estratégias de negociação. Ao encontrar problemas detalhados semelhantes no design de estratégias, soluções razoáveis podem ser imediatamente concebidas. A plataforma de negociação quantitativa Inventor é usada como uma plataforma para explicação, teste e prática. Linguagem de programação de políticas: JavaScript Mercado de negociação: mercado de ativos blockchain (BTC, ETH, etc.)
Normalmente, dependendo da lógica da estratégia, é possível usar as seguintes interfaces diferentes para obter dados de mercado, porque a lógica de negociação da estratégia geralmente é orientada por dados de mercado (claro, existem algumas estratégias que não olham para o mercado , como estratégias de investimento fixo).
GetTicker: Obtenha informações sobre ticks em tempo real. Geralmente é usado para obter rapidamente o preço mais recente, o preço de compra e o preço de venda.
GetDepth: Obtenha cotações de profundidade do livro de ordens. Geralmente é usado para obter o preço de cada nível e o tamanho do pedido. Usado para estratégias de hedge, estratégias de criação de mercado, etc.
GetTrade: Obtenha os últimos registros de transações do mercado. É geralmente usado para analisar o comportamento do mercado em um curto período de tempo e analisar micromudanças de mercado. Geralmente usado em estratégias de alta frequência e estratégias algorítmicas.
GetRecords: Obtenha dados de mercado da linha K. Frequentemente usado em estratégias de acompanhamento de tendências. Usado para calcular indicadores.
Ao elaborar estratégias, os novatos geralmente ignoram diversas situações de erro e acreditam intuitivamente que os resultados de cada elo da estratégia são predeterminados. Mas, na verdade, esse não é o caso. Ao solicitar dados de mercado durante a operação do programa de estratégia, várias situações inesperadas serão encontradas. Por exemplo, algumas interfaces de mercado retornam dados anormais:
var depth = exchange.GetDepth()
// depth.Asks[0].Price < depth.Bids[0].Price 卖一价格低于了买一价格,这种情况不可能存在于盘面上,
// 因为卖出的价格低于买入的价格,必定已经成交了。
// depth.Bids[n].Amount = 0 订单薄买入列表第n档,订单量为0
// depth.Asks[m].Price = 0 订单薄卖出列表第m档,订单价格为0
Ou exchange.GetDepth() retorna diretamente um valor nulo.
Há muitas situações estranhas como essa. Portanto, o processamento correspondente deve ser feito para esses problemas previsíveis, e esse tipo de solução de processamento é chamado de processamento tolerante a falhas.
A abordagem usual de tolerância a falhas é descartar dados e recuperá-los novamente.
Por exemplo:
function main () {
while (true) {
onTick()
Sleep(500)
}
}
function GetTicker () {
while (true) {
var ticker = exchange.GetTicker()
if (ticker.Sell > ticker.Buy) { // 以 检测卖一价格是不是小于买一价这个错误的容错处理为例,
// 排除这个错误,当前函数返回 ticker 。
return ticker
}
Sleep(500)
}
}
function onTick () {
var ticker = GetTicker() // 确保获取到的 ticker 不会存在 卖一价格小于买一价格这种数据错误的情况。
// ... 具体的策略逻辑
}
Outros processos previsíveis tolerantes a falhas podem ser tratados de maneira semelhante. O princípio do design é que dados incorretos nunca devem ser usados para orientar a lógica da estratégia.
Aquisição de dados da linha K, ligue para:
var r = exchange.GetRecords()
Os dados obtidos da linha K são uma matriz, como esta:
[
{"Time":1562068800000,"Open":10000.7,"High":10208.9,"Low":9942.4,"Close":10058.8,"Volume":6281.887000000001},
{"Time":1562072400000,"Open":10058.6,"High":10154.4,"Low":9914.5,"Close":9990.7,"Volume":4322.099},
...
{"Time":1562079600000,"Open":10535.1,"High":10654.6,"Low":10383.6,"Close":10630.7,"Volume":5163.484000000004}
]
Você pode ver que cada chave{}Inclui hora, preço de abertura (open), preço mais alto (high), preço mais baixo (low), preço de fechamento (close) e volume de negociação (volume).
Isto é um castiçal. Geralmente, os dados da linha K são usados para calcular indicadores, como média móvel MA, MACD, etc.
Insira os dados da linha K como parâmetros (dados da matéria-prima), defina os parâmetros do indicador e calcule a função dos dados do indicador, que chamamos de função do indicador.
Há muitas funções indicadoras na Plataforma de Negociação Quantitativa Inventor.
Por exemplo, quando calculamos o indicador de média móvel, com base nos diferentes períodos dos dados da linha K que passamos, calculamos a média móvel do período correspondente. Por exemplo, se os dados diários da linha K forem passados (uma barra da linha K representa um dia), o indicador calculado é a média móvel diária. Da mesma forma, se os dados da linha K forem passados para a função do indicador de média móvel for um período de 1 hora, então o indicador calculado é a média móvel de 1 hora.
Geralmente, quando calculamos indicadores, muitas vezes ignoramos um problema. Se eu quiser calcular o indicador de média móvel de 5 dias, então primeiro preparamos os dados diários da linha K:
var r = exchange.GetRecords(PERIOD_D1) // 给GetRecords 函数传入参数 PERIOD_D1就是指定获取日K线,
// 具体函数使用可以参看:https://www.fmz.com/api#GetRecords
Com os dados diários da linha K, podemos calcular o indicador de média móvel. Se quisermos calcular a média móvel de 5 dias, precisamos definir o parâmetro indicador da função indicadora como 5.
var ma = TA.MA(r, 5) // TA.MA() 就是指标函数,用来计算均线指标,第一个参数设置刚才获取的日K线数据r,
// 第二个参数设置5,计算出来的就是5日均线,其它指标函数同理。
Nós negligenciamos um problema potencial. E se o número de barras K-line nos dados K-line r-day for menor que 5? Podemos calcular um indicador de média móvel de 5 dias válido? A resposta é definitivamente não. Porque o indicador de média móvel serve para encontrar a média dos preços de fechamento de um certo número de barras da linha K.

Portanto, antes de usar dados de linha K e funções indicadoras para calcular dados indicadores, é necessário determinar se o número de colunas de linha K nos dados de linha K atende às condições para o cálculo do indicador (parâmetros do indicador)
Portanto, antes de calcular a média móvel de 5 dias, é necessário fazer um julgamento. O código completo é o seguinte:
function CalcMA () {
var r = _C(exchange.GetRecords, PERIOD_D1) // _C() 是容错函数,目的就是避免 r 为 null , 具体可以查询文档:https://www.fmz.com/api#_C
if (r.length > 5) {
return TA.MA(r, 5) // 用均线指标函数 TA.MA 计算出均线数据,做为函数返回值,返回。
}
return false
}
function main () {
var ma = CalcMA()
Log(ma)
}

O backtesting mostra: [null,null,null,null,4228.7,4402.9400000000005, … ]
Pode-se observar que os quatro primeiros indicadores da média móvel de 5 dias calculada são nulos, porque o número de colunas da linha K é menor que 5 e a média não pode ser calculada. Até a 5ª vela, isso pode ser calculado.
Quando escrevemos algumas estratégias, geralmente há um cenário em que precisamos processar algumas operações ou imprimir alguns logs quando cada ciclo da linha K é concluído. Como alcançamos essa funcionalidade? Para iniciantes que não têm experiência em programação, eles podem não conseguir pensar em qual mecanismo usar para lidar com isso. Aqui, daremos algumas dicas diretamente.
Podemos julgar que um ciclo de coluna K-line é concluído começando com o atributo de tempo nos dados K-line. Cada vez que obtemos um dado K-line, julgamos o atributo Tempo nos dados da última coluna K-line desses dados da linha K. Se esse valor de atributo foi alterado. Se ele foi alterado, significa que uma nova coluna da linha K foi gerada (provando que o ciclo anterior da coluna da linha K da coluna da linha K recém-gerada foi foi concluído). Se não mudou, significa que não há Um novo candlestick é gerado (o último ciclo atual do candlestick ainda não foi concluído).
Então, precisamos de uma variável para registrar o tempo da última coluna de velas dos dados das velas.
var r = exchange.GetRecords()
var lastTime = r[r.length - 1].Time // lastTime 用来记录最后一根K线柱的时间。
Em aplicações práticas, a estrutura geralmente é assim:
function main () {
var lastTime = 0
while (true) {
var r = _C(exchange.GetRecords)
if (r[r.length - 1].Time != lastTime) {
Log("新K线柱产生")
lastTime = r[r.length - 1].Time // 一定要更新 lastTime ,这个至关重要。
// ... 其它处理逻辑
// ...
}
Sleep(500)
}
}

Pode-se observar que no backtest, o período da linha K é definido como o dia (a função exchange.GetRecords é chamada sem especificar parâmetros, e o período da linha K definido de acordo com o backtest é o parâmetro padrão). Sempre que um aparece uma nova coluna K-line, é impresso um log.
Se você quiser exibir ou controlar o tempo que uma estratégia leva para acessar a interface da exchange, você pode usar o seguinte código:
function main () {
while (true) {
var beginTime = new Date().getTime()
var ticker = exchange.GetTicker()
var endTime = new Date().getTime()
LogStatus(_D(), "GetTicker() 函数耗时:", endTime - beginTime, "毫秒")
Sleep(1000)
}
}
Simplificando, o registro de data e hora registrado após a chamada da função GetTicker é subtraído do registro de data e hora anterior à chamada para calcular o número de milissegundos decorridos, ou seja, o tempo que a função GetTicker leva para ser executada e retornar o resultado.
Se você quiser um limite superior numérico, normalmente você usa Math.min para limitar
Por exemplo, ao fazer uma ordem de venda, o valor da ordem não deve ser maior que o número de moedas na conta. Porque se for maior que a quantidade de moedas disponíveis na conta, será reportado um erro ao efetuar uma ordem.
Geralmente controlado assim: Por exemplo, você planeja colocar uma ordem de venda de 0,2 moedas.
var planAmount = 0.2
var account = _C(exchange.GetAccount)
var amount = Math.min(account.Stocks, planAmount)
Isso garante que o valor do pedido a ser feito não exceda o número de moedas disponíveis na conta.
Da mesma forma, Math.max é usado para garantir um limite inferior para um valor. A que tipo de cenários isso geralmente se aplica? Geralmente, as exchanges têm um limite mínimo de quantidade de pedido para certos pares de negociação. Se a quantidade do pedido for menor que essa quantidade mínima de pedido, o pedido será rejeitado. Dessa forma o pedido falhará. Suponha que a quantidade mínima de pedido para BTC seja normalmente 0,01. Às vezes, a estratégia de negociação pode calcular que a quantidade do pedido é menor que 0,01, então podemos usar Math.max para garantir a quantidade mínima do pedido.
Pode ser usado_Função N() ou função SetPrecision para controlar a precisão.
A função SetPrecision() só precisa ser definida uma vez, e o sistema truncará automaticamente as casas decimais excedentes nos valores de quantidade e preço do pedido.
_A função N() é usada para truncar um valor para um certo número de casas decimais (controle de precisão)
Por exemplo:
var pi = _N(3.141592653, 2)
Log(pi)
O valor de pi é truncado para 2 casas decimais, que é: 3,14
Consulte a documentação da API para obter detalhes.
Você pode usar esse mecanismo para usar o método de detecção de timestamp para determinar o timestamp atual menos o timestamp da última vez que a tarefa agendada foi concluída e calcular o tempo decorrido em tempo real. Quando o tempo decorrido excede um determinado período de tempo definido Depois disso , a nova operação é executada.
Por exemplo, ele é usado em estratégias de investimento fixo.
var lastActTime = 0
var waitTime = 1000 * 60 * 60 * 12 // 一天的毫秒数
function main () {
while (true) {
var nowTime = new Date().getTime()
if (nowTime - lastActTime > waitTime) {
Log("执行定投")
// ... 具体的定投操作,买入操作。
lastActTime = nowTime
}
Sleep(500)
}
}
Este é um exemplo simples.
Usando a função quantizada _G() do inventor e a função de saída e salvamento, é muito conveniente projetar uma estratégia para sair e salvar o progresso e reiniciar para restaurar automaticamente o status.
var hold = {
price : 0,
amount : 0,
}
function main () {
if (_G("hold")) {
var ret = _G("hold")
hold.price = ret.price
hold.amount = ret.amount
Log("恢复 hold:", hold)
}
var count = 1
while (true) {
// ... 策略逻辑
// ... 策略运行中,可能开仓,交易,把开仓的持仓价格赋值给 hold.price ,开仓的数量赋值给 hold.amount,用以记录持仓信息。
hold.price = count++ // 模拟一些数值
hold.amount = count/10 // 模拟一些数值
Sleep(500)
}
}
function onexit () { // 点击机器人上的停止按钮,会触发执行这个函数,执行完毕机器人停止。
_G("hold", hold)
Log("保存 hold:", JSON.stringify(hold))
}

Pode ser visto que cada vez que o robô é parado, os dados no objeto hold são salvos. Cada vez que ele é reiniciado, os dados são lidos e o valor hold é restaurado ao estado da parada anterior. Claro, o acima é um exemplo simples. Se for usado em uma estratégia real, ele deve ser projetado de acordo com os dados-chave que precisam ser restaurados na estratégia (geralmente informações de conta, posições, valores de lucro, direções de negociação, etc. .). Claro, você também pode definir algumas condições para determinar se deseja restaurar.
Acima estão algumas dicas para desenvolver estratégias. Espero que sejam úteis para iniciantes e desenvolvedores de estratégias! A maneira mais rápida de melhorar é praticar! Desejo a todos lucros contínuos.