
Mi buen amigo Ran ha estado observando este indicador durante mucho tiempo y me lo recomendó antes de Año Nuevo para discutir si se puede convertir en una forma cuantitativa. Lamentablemente, sufrí de procrastinación y no lo ayudé a cumplir su deseo hasta ahora. De hecho, mi comprensión de los algoritmos ha avanzado mucho recientemente. Planeo escribir un traductor de pino algún día. Todo puede ser Python. . Bueno, sin más preámbulos, permítanos presentarle esta legendaria línea de súper tendencia. .
La nueva generación del sistema de trading inteligente de CMC Markets: Supertrend
Aquí hay un artículo que presenta este sistema.

En la nueva generación del sistema de comercio inteligente de CMC Markets, seleccione “Super Trend Line” en los indicadores técnicos para activarlo. Como se muestra en la figura, puede ajustar el “color y el grosor” de las señales ascendentes y descendentes según sus preferencias. Entonces, ¿qué es el indicador de supertendencia? Antes de comprender la fórmula del indicador de supertendencia, es necesario comprender el ATR porque la supertendencia utiliza valores de ATR para calcular los valores del indicador.
El algoritmo principal también se presenta en la imagen siguiente.

A primera vista, la descripción principal es un canal de HL2 (precio promedio de K-line) multiplicado por n veces ATR. Hacer una ruptura de tendencia.
Pero el artículo es bastante breve. No existe un algoritmo detallado. Entonces pensé en la mejor comunidad Tradingview.
No es sorprendente. Por supuesto que está ahí.

A juzgar por el gráfico, es bastante consistente con la tendencia. Pero desafortunadamente es sólo una señal de alerta.
El código no parece demasiado largo, así que tradujémoslo y probémoslo. ! (¡Qué rico!) ¡Qué rico!
El código completo de pino es el anterior. .
Aquí creamos una nueva estrategia en FMZ y la llamamos SuperTrade

A continuación, establecemos dos parámetros Factor y Pd

Para simplificar mejor el funcionamiento del código y hacerlo más fácil de entender, necesitamos utilizar el paquete de expansión de datos avanzado de Python.pandas
Durante el almuerzo, le pregunté al profesor Mengmeng si FMZ apoya esta biblioteca. Lo revisé por la tarde y realmente funcionaba. El maestro Mengmeng es realmente asombroso.
1. Necesitamos importar la biblioteca de tiempo de la biblioteca pandas 2. Configure el contrato trimestral en la función principal (ejecutando principalmente OKEX) 3. Establezca un bucle doTicker() para realizar la prueba una vez cada 15 minutos. Ejecute el código en un ciclo de 15 minutos A continuación escribimos la estrategia principal en doTicker().
import pandas as pd
import time
def main():
exchange.SetContractType("quarter")
preTime = 0
Log(exchange.GetAccount())
while True:
records = exchange.GetRecords(PERIOD_M15)
if records and records[-2].Time > preTime:
preTime = records[-2].Time
doTicker(records[:-1])
Sleep(1000 *60)
4. Necesitamos recuperar el OHCLV de la línea K, así que usemos GetRecords() 5. Importamos los datos recuperados a pandas M15 = pd.DataFrame(records) 6. Necesitamos modificar la etiqueta del encabezado de la tabla. M15.columnas =[‘time’,‘open’,‘high’,‘low’,‘close’,‘volume’,‘OpenInterest’] De hecho, solo cambia las primeras letras de ‘open’, ‘high’, ‘low’ y ‘close’ a minúsculas, de modo que sea más fácil escribir código más tarde sin alternar entre mayúsculas y minúsculas.
def doTicker(records):
M15 = pd.DataFrame(records)
M15.columns = ['time','open','high','low','close','volume','OpenInterest']
7. Agregue una columna hl2 al conjunto de datos hl2=(high+low)/2
#HL2
M15['hl2']=(M15['high']+M15['low'])/2
8. A continuación, calculemos el ATR Debido a que el cálculo de ATR requiere la importación de una longitud variable, su valor es Pd
Luego nos remitimos al manual del lenguaje Mai, y los pasos del algoritmo del promedio de volatilidad real de ATR son los siguientes: TR : MAX(MAX((HIGH-LOW),ABS(REF(CLOSE,1)-HIGH)),ABS(REF(CLOSE,1)-LOW)); ATR : RMA(TR,N)
El valor TR es la mayor de las tres diferencias siguientes. 1. La fluctuación entre el precio más alto y el precio más bajo del día de negociación actual ALTO-BAJO 2. La fluctuación entre el precio de cierre del día de negociación anterior y el precio más alto del día de negociación actual (REF(CLOSE,1)-HIGH) 3. La fluctuación entre el precio de cierre del día de negociación anterior y el precio más bajo del día de negociación actual (REF(CLOSE,1)-LOW) Entonces TR: MAX(MAX((ALTO-BAJO),ABS(REF(CERRAR,1)-ALTO)),BS(REF(CERRAR,1)-BAJO));
En los cálculos de Python
M15['prev_close']=M15['close'].shift(1)
Primero, configure un prev_close para obtener los datos de cierre en la fila anterior, es decir, mueva el cierre hacia la derecha 1 cuadrícula para formar un nuevo parámetro
ranges= [M15['high'] - M15['low'],M15['high']-M15['prev_close'],M15['low']-M15['prev_close']]
A continuación, defina una variable intermedia para registrar la matriz de tres valores de comparación de TR. (ALTO-BAJO)(alto-prev_close)(bajo-prev_close)
M15['tr'] = pd.DataFrame(ranges).T.abs().max(axis=1)
Definimos una nueva columna denominada TR en el conjunto de datos. El valor de TR es el valor absoluto máximo de la variable intermedia. Utilizamos las funciones abs() y max().
alpha = (1.0 / length) if length > 0 else 0.5
M15['atr']=M15['tr'].ewm(alpha=alpha, min_periods=length).mean()
Finalmente, necesitamos calcular el valor de ATR, ATR: RMA (TR, N). Se ha descubierto que el algoritmo RMA es en realidad una variante de valor fijo del algoritmo EMA. N es la variable que importamos, donde el parámetro predeterminado para ATR es 14. Aquí importamos alfa = el recíproco de la longitud.
===
Luego use el algoritmo EWM para calcular la EMA El proceso completo de cálculo del ATR es el siguiente
#ATR(PD)
length=Pd
M15['prev_close']=M15['close'].shift(1)
ranges= [M15['high'] - M15['low'],M15['high']-M15['prev_close'],M15['low']-M15['prev_close']]
M15['tr'] = pd.DataFrame(ranges).T.abs().max(axis=1)
alpha = (1.0 / length) if length > 0 else 0.5
M15['atr']=M15['tr'].ewm(alpha=alpha, min_periods=length).mean()
9 Comience a calcular Up y Dn
M15['Up']=M15['hl2']-(Factor*M15['atr'])
M15['Dn']=M15['hl2']+(Factor*M15['atr'])
Up=hl2 -(Factor * atr) Dn=hl2 +(Factor * atr) ¿No es sencillo?
El siguiente es el segmento de código central de las líneas 15 a 21 en TV
TrendUp=close[1]>TrendUp[1]? max(Up,TrendUp[1]) : Up
TrendDown=close[1]<TrendDown[1]? min(Dn,TrendDown[1]) : Dn
Trend = close > TrendDown[1] ? 1: close< TrendUp[1]? -1: nz(Trend[1],1)
Tsl = Trend==1? TrendUp: TrendDown
linecolor = Trend == 1 ? green : red
El propósito principal de este párrafo es expresar, Si está en una fase alcista, (línea inferior) TrendUp = max(Up,TrendUp[1]) Si está en la etapa descendente, (línea superior) TrendDown=min(Dn,TrendDown[1]) Es decir, en una tendencia, el valor ATR ha estado utilizando una técnica similar a la estrategia Bandit Bollinger. Sigue estrechando el otro lado del canal.
Aquí, cada cálculo de TrendUp y TrendDown debe iterarse automáticamente. Es decir, cada paso debe calcularse en función del paso anterior. Entonces necesitamos recorrer el conjunto de datos.
Aquí primero debemos crear nuevos campos TrendUp, TrendDown, Trend y linecolor para el conjunto de datos. Y darles un valor inicial Luego use la sintaxis fillna(0) para llenar los datos con valores nulos en los resultados calculados previamente con 0
M15['TrendUp']=0.0
M15['TrendDown']=0.0
M15['Trend']=1
M15['Tsl']=0.0
M15['linecolor']='Homily'
M15 = M15.fillna(0)
Iniciar un bucle for Uso de operaciones ternarias de Python en bucles
for x in range(len(M15)):
Cálculo de TrendUp TrendUp = MAX(Up,TrendUp[-1]) if close[-1]>TrendUp[-1] else Up El significado general es que si el cierre anterior > el TrendUp anterior, si es verdadero, toma el valor máximo de Up y el TrendUp anterior, si no es verdadero, toma el valor de Up y lo pasa al TrendUp actual.
M15['TrendUp'].values[x] = max(M15['Up'].values[x],M15['TrendUp'].values[x-1]) if (M15['close'].values[x-1]>M15['TrendUp'].values[x-1]) else M15['Up'].values[x]
De manera similar, calcule TrendDown TrendDown=min(Dn,TrendDown[-1]) if close[-1]
M15['TrendDown'].values[x] = min(M15['Dn'].values[x],M15['TrendDown'].values[x-1]) if (M15['close'].values[x-1]<M15['TrendDown'].values[x-1]) else M15['Dn'].values[x]
A continuación se muestra la bandera para calcular la dirección de control. Simplifiqué el pseudocódigo. Trend= 1 if (close > TrendDown[-1]) else (x) x = -1 if (close< TrendUp[-1]) else Trend[-1]
El significado es que si el precio de cierre > la tendencia bajista anterior, entonces tome 1 (alcista) si no, tome x Si el precio de cierre es menor que la tendencia alcista anterior, se toma -1 (posición corta). Si no, se toma la tendencia anterior (lo que significa que permanece sin cambios). Traducido al lenguaje gráfico, significa romper el riel superior para cambiar la bandera a alcista, romper el riel inferior para cambiar la bandera a bajista y los demás tiempos permanecen sin cambios.
M15['Tsl'].values[x] = M15['TrendUp'].values[x] if (M15['Trend'].values[x]==1) else M15['TrendDown'].values[x]
Calcular Tsl y Linecolor Tsl= rendUp if (Trend==1) else TrendDown Tsl se utiliza para representar el valor de SuperTrend en el gráfico. Esto significa que cuando eres alcista, marca la pista inferior en el gráfico, y cuando eres bajista, marca la pista superior en el gráfico. linecolor= ‘green’ if (Trend==1) else ‘red’ El significado de linecolor es: si eres alcista, marca la línea verde; si eres bajista, marca el color vacío (se usa principalmente para la visualización de Tradingview)
M15['Tsl'].values[x] = M15['TrendUp'].values[x] if (M15['Trend'].values[x]==1) else M15['TrendDown'].values[x]
M15['linecolor'].values[x]= 'green' if ( M15['Trend'].values[x]==1) else 'red'
Las siguientes líneas 23-30 son principalmente para trazar un gráfico, que no se explicará en detalle aquí.
Finalmente, hay 2 líneas de código para el control de señales de compra y venta. En Tradingview, significa dar una señal después de revertir la bandera. Convertir declaraciones condicionales a Python. Si la bandera de tendencia anterior cambia de -1 a 1, significa que se ha roto la resistencia superior. Abra una posición larga. Si la bandera de tendencia anterior cambia de 1 a -1, significa que se ha roto el soporte bajista. Abra una posición corta.
if(M15['Trend'].values[-1] == 1 and M15['Trend'].values[-2] == -1):
Log('SuperTrend V.1 Alert Long',"Create Order Buy)
if(M15['Trend'].values[-1] == -1 and M15['Trend'].values[-2] == 1):
Log('SuperTrend V.1 Alert Long',"Create Order Sell)
El código completo para esta sección es el siguiente:
M15['TrendUp']=0.0
M15['TrendDown']=0.0
M15['Trend']=1
M15['Tsl']=0.0
M15['linecolor']='Homily'
M15 = M15.fillna(0)
for x in range(len(M15)):
M15['TrendUp'].values[x] = max(M15['Up'].values[x],M15['TrendUp'].values[x-1]) if (M15['close'].values[x-1]>M15['TrendUp'].values[x-1]) else M15['Up'].values[x]
M15['TrendDown'].values[x] = min(M15['Dn'].values[x],M15['TrendDown'].values[x-1]) if (M15['close'].values[x-1]<M15['TrendDown'].values[x-1]) else M15['Dn'].values[x]
M15['Trend'].values[x] = 1 if (M15['close'].values[x] > M15['TrendDown'].values[x-1]) else ( -1 if (M15['close'].values[x]< M15['TrendUp'].values[x-1])else M15['Trend'].values[x-1] )
M15['Tsl'].values[x] = M15['TrendUp'].values[x] if (M15['Trend'].values[x]==1) else M15['TrendDown'].values[x]
M15['linecolor'].values[x]= 'green' if ( M15['Trend'].values[x]==1) else 'red'
if(M15['Trend'].values[-1] == 1 and M15['Trend'].values[-2] == -1):
Log('SuperTrend V.1 Alert Long',"Create Order Buy)
Log('Tsl=',Tsl)
if(M15['Trend'].values[-1] == -1 and M15['Trend'].values[-2] == 1):
Log('SuperTrend V.1 Alert Long',"Create Order Sell)
Log('Tsl=',Tsl)


Ajusté la estructura general del código. Y fusionar las instrucciones de órdenes relacionadas largas y cortas en la estrategia. Aquí está el código completo
'''backtest
start: 2019-05-01 00:00:00
end: 2020-04-21 00:00:00
period: 15m
exchanges: [{"eid":"Futures_OKCoin","currency":"BTC_USD"}]
'''
import pandas as pd
import time
def main():
exchange.SetContractType("quarter")
preTime = 0
Log(exchange.GetAccount())
while True:
records = exchange.GetRecords(PERIOD_M15)
if records and records[-2].Time > preTime:
preTime = records[-2].Time
doTicker(records[:-1])
Sleep(1000 *60)
def doTicker(records):
#Log('onTick',exchange.GetTicker())
M15 = pd.DataFrame(records)
#Factor=3
#Pd=7
M15.columns = ['time','open','high','low','close','volume','OpenInterest']
#HL2
M15['hl2']=(M15['high']+M15['low'])/2
#ATR(PD)
length=Pd
M15['prev_close']=M15['close'].shift(1)
ranges= [M15['high'] - M15['low'],M15['high']-M15['prev_close'],M15['low']-M15['prev_close']]
M15['tr'] = pd.DataFrame(ranges).T.abs().max(axis=1)
alpha = (1.0 / length) if length > 0 else 0.5
M15['atr']=M15['tr'].ewm(alpha=alpha, min_periods=length).mean()
M15['Up']=M15['hl2']-(Factor*M15['atr'])
M15['Dn']=M15['hl2']+(Factor*M15['atr'])
M15['TrendUp']=0.0
M15['TrendDown']=0.0
M15['Trend']=1
M15['Tsl']=0.0
M15['linecolor']='Homily'
M15 = M15.fillna(0)
for x in range(len(M15)):
M15['TrendUp'].values[x] = max(M15['Up'].values[x],M15['TrendUp'].values[x-1]) if (M15['close'].values[x-1]>M15['TrendUp'].values[x-1]) else M15['Up'].values[x]
M15['TrendDown'].values[x] = min(M15['Dn'].values[x],M15['TrendDown'].values[x-1]) if (M15['close'].values[x-1]<M15['TrendDown'].values[x-1]) else M15['Dn'].values[x]
M15['Trend'].values[x] = 1 if (M15['close'].values[x] > M15['TrendDown'].values[x-1]) else ( -1 if (M15['close'].values[x]< M15['TrendUp'].values[x-1])else M15['Trend'].values[x-1] )
M15['Tsl'].values[x] = M15['TrendUp'].values[x] if (M15['Trend'].values[x]==1) else M15['TrendDown'].values[x]
M15['linecolor'].values[x]= 'Long' if ( M15['Trend'].values[x]==1) else 'Short'
linecolor=M15['linecolor'].values[-2]
close=M15['close'].values[-2]
Tsl=M15['Tsl'].values[-2]
if(M15['Trend'].values[-1] == 1 and M15['Trend'].values[-2] == -1):
Log('SuperTrend V.1 Alert Long','Create Order Buy')
Log('Tsl=',Tsl)
position = exchange.GetPosition()
if len(position) > 0:
Amount=position[0]["Amount"]
exchange.SetDirection("closesell")
exchange.Buy(_C(exchange.GetTicker).Sell*1.01, Amount);
exchange.SetDirection("buy")
exchange.Buy(_C(exchange.GetTicker).Sell*1.01, vol);
if(M15['Trend'].values[-1] == -1 and M15['Trend'].values[-2] == 1):
Log('SuperTrend V.1 Alert Long','Create Order Sell')
Log('Tsl=',Tsl)
position = exchange.GetPosition()
if len(position) > 0:
Amount=position[0]["Amount"]
exchange.SetDirection("closebuy")
exchange.Sell(_C(exchange.GetTicker).Buy*0.99,Amount);
exchange.SetDirection("sell")
exchange.Sell(_C(exchange.GetTicker).Buy*0.99, vol*2);
Enlace de estrategia pública: https://www.fmz.com/strategy/200625
Seleccionamos datos del año pasado para realizar pruebas retrospectivas. Utilice el contrato trimestral de OKEX con un ciclo de 15 minutos. Los parámetros establecidos son, Factor=3 Pd=45 vol=100 (100 tickets por pedido) La rentabilidad anualizada es de aproximadamente el 33%. En general, el retroceso no es muy grande. La principal causa de esto es el accidente del 312 que tuvo un impacto significativo en el sistema. Si no existiera el 312 los rendimientos serían mejores.

SuperTrend es un muy buen sistema de trading
El principio principal del sistema SuperTrend es utilizar la estrategia de ruptura del canal ATR (similar al canal Kent) Pero el cambio principal radica en el uso de la estrategia de estrechamiento de Bandit Bollinger, o el principio de Donchian inverso. Los canales superior e inferior se estrechan constantemente durante el funcionamiento del mercado. Para lograr la operación de ruptura y giro del canal. (Una vez que el canal se rompe, los rieles superior e inferior vuelven a sus valores iniciales)
Grafiqué TrendUp TrendDn por separado en TradingView
Esto te ayudará a comprender mejor esta estrategia.
Claro de un vistazo

También hay una versión js en github. No entiendo muy bien js, pero a juzgar por la declaración if, parece haber algún problema. La dirección eshttps://github.com/Dodo33/gekko-supertrend-strategy/blob/master/Supertrend.js
Finalmente encontré la versión original. Fue publicado el 29/05/2013. Escrito por Rajandran R Código C++ publicado en el foro Mt4https://www.mql5.com/en/code/viewcode/10851/128437/Non_Repainting_SuperTrend.mq4 Entiendo aproximadamente el significado de C++ y lo reescribiré cuando tenga la oportunidad.
Espero que todos puedan aprender la esencia de esto. Es difícil. ~!