O objetivo deste artigo é descrever alguma experiência no desenvolvimento de estratégias, bem como algumas dicas, que permitirão aos leitores entender rapidamente o ponto chave do desenvolvimento de estratégias de negociação.
Quando encontramos detalhes semelhantes em algum projeto de estratégia, podemos imediatamente encontrar uma solução razoável.
Usamos a plataforma FMZ Quant como exemplo para explicação, teste e prática.
Estratégia linguagem de programação vamos usar JavaScript
Para o alvo de negociação, tomamos o mercado de ativos blockchain (BTC, ETH, etc) como nosso objeto
Normalmente, dependendo da lógica da estratégia, ele pode usar as seguintes interfaces diferentes para obter dados de mercado, a maioria das lógicas de estratégia são impulsionadas por dados de mercado (é claro, algumas estratégias não se preocupam com os dados de preço, como uma estratégia de investimento fixo).
GetTicker: Obtenha citações em tempo real.
Geralmente usado para obter rapidamente o último preço atual,
Obtenha a profundidade dos pedidos do livro de pedidos. Geralmente utilizado para obter o preço de cada camada da profundidade da carteira de ordens e o tamanho das ordens pendentes.
GetTrade: Obtenha o último registro de transações do mercado. Geralmente usado para analisar o comportamento do mercado em um curto ciclo de tempo e analisar mudanças microscópicas no mercado.
GetRecords: Obtenha dados de linha K do mercado. Geralmente usado para estratégias de rastreamento de tendências e para calcular indicadores.
Ao projetar a estratégia, o iniciante geralmente ignora os vários erros e acredita intuitivamente que os resultados de cada parte da estratégia são estabelecidos.
Por exemplo, algumas interfaces de mercado devolvem dados não executados:
var depth = exchange.GetDepth()
// depth.Asks[0].Price < depth.Bids[0].Price "Selling 1" price is lower than "buying 1" price, this situation cannot exist on the market.
// Because the selling price is lower than the buying price, the order must have been executed.
// depth.Bids[n].Amount = 0 Order book buying list "nth" layer, order quantity is 0
// depth.Asks[m].Price = 0 Order book selling list "mth" layer, the order price is 0
Ou diretamente exchange.GetDepth() retorna um valor nulo.
O problema é que, no que se refere ao processo de produção, o processo de fabricação é muito mais complexo do que o processo de fabricação.
A maneira normal de lidar com falhas é descartar dados e recuperá-los.
Por exemplo:
function main () {
while (true) {
onTick()
Sleep(500)
}
}
function GetTicker () {
while (true) {
var ticker = exchange.GetTicker()
if (ticker.Sell > ticker.Buy) { // Take the example of fault-tolerant processing that detects whether the "Selling 1" price is less than the "Buying 1" price.
// Exclude this error, the current function returns "ticker".
Return ticker
}
Sleep(500)
}
}
function onTick () {
var ticker = GetTicker() // Make sure the "ticker" you get doesn't exist the situation that "Selling 1" price is less than the "Buying 1" price.
// ... specific strategy logic
}
Uma abordagem semelhante pode ser utilizada para outros processos previsíveis de tolerância a falhas.
O princípio do design é que nunca se pode usar a lógica errada para conduzir a lógica estratégica.
Aquisição de dados da linha K, chamada:
var r = exchange.GetRecords()
Os dados de linha K obtidos 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 brace {} contém tempo, preço de abertura, preço mais alto, preço mais baixo, preço de fechamento e volume.
Esta é uma barra de linha K. Os dados gerais da linha K são usados para calcular indicadores como médias móveis, MACD e assim por diante.
Os dados da linha K são passados como um parâmetro (dados de matéria-prima), e então os parâmetros do indicador são definidos para calcular a função dos dados do indicador, que chamamos de função do indicador.
Há muitas funções de indicador na plataforma de negociação quantitativa FMZ Quant.
Por exemplo, calculamos o indicador da média móvel. De acordo com o ciclo dos dados da linha K, calculamos a média móvel do ciclo correspondente.
Por exemplo, os dados da linha K de passagem (uma barra de linha K representa um dia), calcula a linha média diária, a mesma coisa, se os dados da linha K da função do indicador da média de passagem for um ciclo de 1 hora, então o indicador calculado é a média móvel de 1 hora.
Normalmente, muitas vezes ignoramos um problema ao calcular o indicador. Se eu quiser calcular o indicador da média móvel de 5 dias, então primeiro preparamos os dados diários da linha K:
var r = exchange.GetRecords(PERIOD_D1) // Pass parameters to the "GetRecords" function "PERIOD_D1" specifies the day K line to be acquired.
// Specific function using method can be seen at: https://www.fmz.com/api#GetRecords
Com os dados diários da linha K, podemos calcular o indicador da média móvel. Se quisermos calcular a média móvel de 5 dias, então temos que definir o parâmetro do indicador da função do indicador para 5.
var ma = TA.MA(r, 5) // "TA.MA()" is the indicator function used to calculate the moving average indicator. The first parameter sets the daily K-line data r just obtained.
// The second parameter is set to 5. The calculated 5-day moving average is the same as the other indicators.
Se o número de barras de linha K nos dados de linha K for menor que 5, o que podemos fazer para calcular uma média móvel válida de 5 dias?
A resposta é que não há nada que possas fazer.
Porque o indicador da média móvel é a média dos preços de fechamento de um certo número de barras de linha K.
Por conseguinte, antes de utilizar os dados da linha K e a função do indicador para calcular os dados do indicador, é necessário determinar se o número de barras da linha K nos dados da linha K satisfaz as condições para o cálculo do indicador (parâmetros do indicador).
Então, antes de calcular a média móvel de 5 dias, você tem que verificar primeiro.
function CalcMA () {
var r = _C(exchange.GetRecords, PERIOD_D1) // _C() is a fault-tolerant function, the purpose is to avoid r being null, you can get more information at: https://www.fmz.com/api#_C
if (r.length > 5) {
Return TA.MA(r, 5) // Calculate the moving average data with the moving average indicator function "TA.MA", return it as a function return value.
}
Return false
}
function main () {
var ma = CalcMA()
Log(ma)
}
Exibição do teste de retorno:
[null,null,null,null,4228.7,4402.9400000000005, ... ]
Você pode ver o indicador de média móvel calculada de 5 dias. Os primeiros quatro são nulos, porque o número de barras da linha K é menor que 5, e a média não pode ser calculada. Quando você chegar à 5a barra da linha K, você pode calcula-lo.
Quando escrevemos a estratégia, muitas vezes temos um cenário, como a estratégia precisa processar algumas operações quando cada ciclo de linha K é concluído, ou imprimir alguns registros.
Como implementamos essas funções? Para iniciantes que não têm experiência em programação, pode ser um problema problemático. Aqui damos as soluções.
Como julgar um ciclo de barra de linha K é concluído. Podemos começar com o atributo de tempo nos dados de linha K. Cada vez que obtermos os dados de linha K, julgaremos se o atributo de tempo da última barra de linha K desses dados de linha K está mudando ou não. Se for alterado, significa que há uma nova barra de linha K gerada (provando que o ciclo de barra de linha K anterior da barra de linha K recém-gerada foi concluído), se não houver mudança, significa que não há nova barra de linha K gerada (o último ciclo de barra de linha K atual ainda não foi concluído).
Então precisamos de uma variável para registrar o tempo da última barra de linha K dos dados de linha K.
var r = exchange.GetRecords()
var lastTime = r[r.length - 1].Time // "lastTime" used to record the last K-line bar time.
Na prática, é geralmente o caso:
function main () {
var lastTime = 0
while (true) {
var r = _C(exchange.GetRecords)
if (r[r.length - 1].Time != lastTime) {
Log ("New K-line bar generated")
lastTime = r[r.length - 1].Time // Be sure to update "lastTime", this is crucial.
// ... other processing logic
// ...
}
Sleep(500)
}
}
Pode-se ver que no backtest, o ciclo da linha K é definido para o diário (o parâmetro não é especificado quando oexchange.GetRecords
A função é chamada, e o ciclo de linha K definido de acordo com o backtest é o parâmetro padrão).
Se você quiser ter uma certa exibição ou controle sobre o tempo necessário para a estratégia acessar a interface do 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() function time-consuming:", endTime - beginTime, "millisecond")
Sleep(1000)
}
}
Simplificando, o carimbo horário gravado após ligar para oGetTicker
A função é subtraída da marca de tempo antes da chamada, e o número de milissegundos experimentados é calculado, ou seja, o tempo tomado peloGetTicker
função da execução para retornar.
Por exemplo, no processo de colocação de uma ordem de venda, o montante da ordem de venda não deve ser superior ao número de moedas na conta. Porque se for maior do que o número de moedas disponíveis na conta, a ordem causará erros.
Controlamos assim:
Por exemplo, nós planejamos vender curto 0.2 moedas.
var planAmount = 0.2
var account = _C(exchange.GetAccount)
var amount = Math.min(account.Stocks, planAmount)
Isto garante que o número de encomendas não exceda o número de moedas disponíveis na conta.
Pela mesma razão,Math.max
É utilizada para assegurar o limite inferior de um valor.
Geralmente, a troca normal tem um limite mínimo de envio de ordens para certos pares comerciais. Se for inferior ao valor mínimo, a ordem será rejeitada. Isso também causará o fracasso do programa.
Supondo que o BTC normalmente tenha uma quantidade mínima de ordem de colocação de 0,01.
Estratégias de negociação podem, por vezes, resultar em menos de 0,01 quantidades de ordem, para que possamos usarMath.max
para assegurar a quantidade mínima de encomenda.
A precisão pode ser controlada através do_N()
função ouSetPrecision
function.
OSetPrecision()
A função só precisa de ser definida uma vez, e o número de casas decimais na quantidade de ordem e valor do preço é automaticamente truncado no sistema.
O_N()
A função é realizar truncamento de ponto decimal (controle de precisão) para um determinado valor.
Por exemplo:
var pi = _N(3.141592653, 2)
Log(pi)
O valor de pi é truncado pela casa decimal, e 2 casas decimais são reservados, que é: 3.14
Ver a documentação da API para mais detalhes.
Você pode usar esse mecanismo para usar o método de detecção de carimbo de tempo para determinar o carimbo de tempo atual menos o carimbo de tempo da última vez que a tarefa programada foi executada, e calcular o tempo decorrido em tempo real. Quando o tempo decorrido exceder um determinado comprimento de tempo definido. Depois disso, uma nova operação é executada.
Por exemplo, utilizado numa estratégia de investimento fixo.
var lastActTime = 0
var waitTime = 1000 * 60 * 60 * 12 // number of milliseconds a day
function main () {
while (true) {
var nowTime = new Date().getTime()
if (nowTime - lastActTime > waitTime) {
Log ("Execution Fixed")
// ... specific fixed investment operation, buying operation.
lastActTime = nowTime
}
Sleep(500)
}
}
Este é um exemplo simples.
Utilizando o FMZ Quant_G()
Função, e saindo da função de salvamento, é conveniente conceber uma estratégia para sair do progresso de salvamento e reiniciar o estado de recuperação automática.
var hold = {
Price : 0,
Amount : 0,
}
function main () {
if (_G("hold")) {
var ret = _G("hold")
hold.price = ret.price
hold.amount = ret.amount
Log("restore hold:", hold)
}
var count = 1
while (true) {
// ... strategy logic
// ... In the strategy operation, it is possible that when opening a position, then assign the position price of the open position to "hold.price", and the amount of open positions is assigned to "hold.amount" to record the position information.
hold.price = count++ // simulate some values
hold.amount = count/10 // Simulate some values
Sleep(500)
}
}
function onexit () { // Click the stop button on the robot to trigger the execution of this function. After the execution, the robot stops.
_G("hold", hold)
Log("save hold:", JSON.stringify(hold))
}
Os dados obtidos nohold
Objeto é salvo cada vez que o robô é parado. e quando cada vez que os dados são reiniciados, os dados são lidos e o valor dohold
é restaurado ao estado anterior à parada.
Naturalmente, o acima é um exemplo simples. se for usado em uma estratégia de negociação real, ele deve ser projetado de acordo com os dados-chave que precisam ser restaurados na estratégia (geralmente são informações de conta, posição, valor de lucro, direção de negociação e assim por diante.).
Além disso, pode também definir algumas outras condições para restaurar.
Estas são algumas dicas para desenvolver uma estratégia de negociação, e espero que possa ajudar os iniciantes!
Treinamento prático é a maneira mais rápida de melhorar a si mesmo! Desejo a todos boa sorte.