2
Suivre
319
Abonnés

Ruée vers l'or de la plateforme Inventor : analyse pratique d'un framework de trading de tendances Python hautement flexible

Créé le: 2025-08-22 17:25:52, Mis à jour le: 2025-08-26 09:56:27
comments   0
hits   468

Je fréquente souvent la plateforme des inventeurs et je trouve toujours des trésors. Aujourd’hui, j’ai trouvé un jeune homme de 21 ans.Stratégie de tendanceJ’admire la structure de code raffinée et parfaite de l’auteur original, ainsi que sa grande flexibilité. La stratégie originale est la version JS, réécrite pour la commodité des utilisateurs de Python.

Honnêtement, de nombreux débutants empruntent de nombreux chemins détournés lorsqu’ils se lancent dans le trading quantitatif. Ils rencontrent souvent des problèmes tels que des ordres échoués, des pertes dues à une mauvaise gestion des risques et des pertes de données après avoir recommencé à utiliser des stratégies. Plus tard, j’ai progressivement compris l’importance d’un bon framework, qui peut nous aider à éviter de nombreux pièges. Ce framework de stratégie de tendance est un outil précieux. C’est plus qu’une simple stratégie de trading ; c’est une véritable boîte à outils, offrant des fonctionnalités basiques mais essentielles comme le placement d’ordres, les ordres stop-loss et la gestion des données. Il suffit de se concentrer sur les questions essentielles : « quand acheter » et « quand vendre ». De plus, le framework est très ouvert, ce qui vous permet de remplacer facilement l’EMA par le MACD, le RSI ou tout autre indicateur de votre choix. Envie de suivre les tendances ? Aucun problème. Envie d’essayer le retour à la moyenne ? Ou même de combiner plusieurs indicateurs ? Absolument. Cette flexibilité est incroyablement utile ; vous pouvez modifier le même code pour expérimenter différentes idées.

Je partage aujourd’hui ce cadre en espérant qu’il sera utile à ceux qui explorent l’investissement quantitatif. Vous trouverez ci-dessous une introduction détaillée à chaque composant de ce cadre, qui, je pense, vous sera utile.

Structure et fonction du cadre

Comparé aux multiples fonctions indépendantes utilisées dans les frameworks de trading multi-matières premières, ce framework vise à organiser et gérer les différentes parties d’une stratégie à l’aide d’un format de classe. Cette conception orientée objet améliore non seulement la maintenabilité et l’évolutivité du code, mais rend également les composants de la stratégie plus modulaires, facilitant ainsi les ajustements et optimisations ultérieurs. Le framework se compose principalement des sections suivantes, chacune ayant sa propre fonction spécifique, garantissant la flexibilité et la praticité de la stratégie.

Initialisation et configuration

fonction init

  • Fonctionnalités__init__Cette fonction est la méthode d’initialisation de la classe de stratégie. Elle permet de définir la configuration de base de la stratégie, d’initialiser les variables et d’obtenir les informations de marché. Elle garantit que les paramètres requis sont configurés avant l’exécution de la stratégie, garantissant ainsi le bon déroulement des opérations de trading ultérieures.
  • Les étapes
    1. Configuration de base: Définissez la devise de trading, le type de contrat, les règles de take-profit et de stop-loss, etc.
    2. Informations sur le marché:Obtenez l’exactitude du prix et de la quantité du contrat pour garantir la légalité de la commande.
    3. Initialisation des variables:Y compris le jugement de tendance, les paramètres stop-profit et stop-loss, les variables statistiques, etc., pour aider les stratégies à prendre des décisions en fonction des conditions du marché.
    4. Paramètres d’échange:Configurez l’interface API d’échange en fonction des informations du marché, telles que la définition de la marge, la précision, etc.

fonction initDatas

  • Fonctionnalités: Initialise les données lorsque la stratégie est en cours d’exécution, y compris les actifs du compte, les statistiques de profit, etc.
  • Les étapes
    1. Gagnez du temps dans l’exécution des politiques.
    2. Lire les données utilisateur locales.
    3. Initialiser les actifs du compte, les statistiques de revenus et d’autres données.
    4. Vérifie si le take profit et le callback take profit sont activés en même temps.

Gestion et stockage des données

fonction saveStrategyRunTime

  • Fonctionnalités: Enregistrez l’heure de début de la politique pour les statistiques et la surveillance ultérieures.
  • Les étapes
    1. Vérifiez si le temps d’exécution est enregistré localement.
    2. Si elle n’est pas enregistrée, enregistrez l’heure actuelle et enregistrez-la localement.
    3. Si elle a été enregistrée, lisez l’heure enregistrée localement.

fonction setStrategyRunTime

  • Fonctionnalités: Définissez l’heure de début de la politique et enregistrez-la dans le stockage local.
  • Les étapes
    1. Utilisation de la plateforme_GLa fonction enregistre localement l’horodatage transmis.
    2. Mettre à jour l’heure de démarrage dans les données de stratégie.

fonction getDaysFromTimeStamp

  • Fonctionnalités: Calcule la différence en jours entre deux horodatages pour calculer la durée d’exécution de la politique.
  • Les étapes
    1. Vérifie si l’heure de fin est antérieure à l’heure de début et renvoie 0 si c’est le cas.
    2. Calcule la différence en secondes entre deux horodatages et la convertit en jours.
    3. Renvoie la différence en jours.

fonction saveUserDatasLocal

  • Fonctionnalités: Enregistrez les données clés pendant l’exécution de la politique sur l’ordinateur local afin qu’elles puissent être restaurées lors du redémarrage de la politique.
  • Les étapes
    1. Comptes d’actifs du package, statistiques de revenus et autres données.
    2. Utilisation de la plateforme_GLa fonction enregistre les données localement.

fonction readUserDataLocal

  • Fonctionnalités:Lire les données utilisateur enregistrées localement pour la récupération des données lorsque la politique est redémarrée.
  • Les étapes
    1. Vérifiez si des données sont enregistrées localement.
    2. Sinon, initialisez les données et enregistrez-les localement.
    3. Si tel est le cas, il est lu et chargé dans la politique.

fonction clearUserDataLocal

  • Fonctionnalités: Effacer les données utilisateur enregistrées localement, généralement utilisées pour la réinitialisation de la politique ou le débogage.
  • Les étapes
    1. Utilisation de la plateforme_GLa fonction efface les données locales.
    2. Opérations de déblaiement des grumes.

Interaction des politiques et traitement des commandes

Fonction runCmd

  • Fonctionnalités: Traite les commandes envoyées par les utilisateurs via l’interface interactive, telles que l’effacement des données locales, la modification de la quantité de commande, etc.
  • Les étapes
    1. Récupérer la commande envoyée par l’utilisateur.
    2. Exécutez les opérations correspondantes en fonction du type de commande, telles que l’effacement des données locales, la modification de la quantité commandée, etc.
    3. Enregistrer les résultats de l’exécution des commandes.

Trading et gestion des commandes

fonction orderDirectly

  • Fonctionnalités:Passer des commandes directement en fonction de la direction et du prix, en prenant en charge les opérations d’ouverture et de clôture.
  • Les étapes
    1. Sélectionnez la fonction de trading en fonction de la direction (achat ou vente).
    2. Définissez la direction du trading.
    3. Exécutez l’opération de commande et renvoyez le résultat.

fonction openLong

  • Fonctionnalités:Ouvrez une position longue et passez un ordre en fonction du prix et de la quantité.
  • Les étapes
    1. Calculez la quantité réelle commandée.
    2. AppelorderDirectlyLa fonction effectue une opération d’achat.

fonction openShort

  • Fonctionnalités:Ouvrez une position courte et passez un ordre en fonction du prix et de la quantité.
  • Les étapes
    1. Calculez la quantité réelle commandée.
    2. AppelorderDirectlyLa fonction effectue une opération de vente.

fonction coverLong

  • Fonctionnalités: Fermez les positions longues et passez des ordres en fonction du prix et de la quantité.
  • Les étapes
    1. AppelorderDirectlyLa fonction effectue une opération de vente.

fonction coverShort

  • Fonctionnalités: Fermez la position courte et passez un ordre en fonction du prix et de la quantité.
  • Les étapes
    1. AppelorderDirectlyLa fonction effectue une opération d’achat.

fonction getRealOrderSize

  • Fonctionnalités:Recalculez la quantité réelle de commande en fonction du prix et de la quantité, et prenez en charge la passation de commandes en fonction du ratio de marge.
  • Les étapes
    1. Calculez la quantité réelle de commande en fonction du fait que la commande est passée selon le ratio de marge.
    2. Renvoie la quantité de commande calculée.

Gestion des risques et statistiques de rendement

fonction getSinglePositionMargin

  • Fonctionnalités:Calculer la marge occupée par une seule position.
  • Les étapes
    1. La marge est calculée en fonction de la direction de la position et de la quantité.
    2. Renvoie le résultat du calcul.

fonction getSinglePositionProfit

  • Fonctionnalités:Calculez le revenu et le rendement d’une position unique.
  • Les étapes
    1. Calculez le profit en fonction de la direction de la position et du prix actuel.
    2. Renvoie les bénéfices et les rendements.

fonction calculateForcedPrice

  • Fonctionnalités:Calcule le prix de liquidation d’une position.
  • Les étapes
    1. Le prix de liquidation est calculé en fonction de la direction de la position et du solde du compte.
    2. Renvoie le résultat du calcul.

fonction getMaxOrderSize

  • Fonctionnalités:Calculez la quantité maximale de commande.
  • Les étapes
    1. La quantité maximale de commande pouvant être passée est calculée en fonction du solde du compte et de l’effet de levier.
    2. Renvoie le résultat du calcul.

fonction getAccountAsset

  • Fonctionnalités:Calcule le total des actifs du compte, y compris les positions et le solde disponible.
  • Les étapes
    1. Calculez le total des capitaux propres en fonction des positions et du solde du compte.
    2. Renvoie le résultat du calcul.

fonction calculerProfit

  • Fonctionnalités:Calculez et enregistrez les rendements de la stratégie.
  • Les étapes
    1. Calculez la différence entre le rendement total actuel et les actifs initiaux.
    2. Enregistrez les gains et mettez à jour les variables statistiques.
    3. Enregistrez les données de revenus localement.

fonction isEnoughAssetToOrder

  • Fonctionnalités:Vérifiez si les fonds du compte sont suffisants pour passer une commande.
  • Les étapes
    1. Obtenez des informations sur le solde du compte.
    2. Calculez les fonds requis en fonction du type de devise de transaction (basé sur l’USDT ou sur les pièces).
    3. Vérifiez si le solde du compte répond aux exigences de la commande.
    4. Renvoie une valeur booléenne indiquant si les fonds sont suffisants.

Jugement de tendance et logique de trading

fonction runInKLinePeriod

  • Fonctionnalités:Déterminez s’il faut exécuter la logique de stratégie en fonction du cycle de la ligne K.
  • Les étapes
    1. Vérifiez si la ligne K actuelle a été traitée.
    2. Si non traité, marquez-le comme traité et renvoyez-leTrueSinon, retourFalse

fonction trendJudgment (module de jugement de tendance principal)

  • FonctionnalitésDéterminez la tendance actuelle à partir d’indicateurs techniques. Ce module est le plus flexible de toute la plateforme. Les utilisateurs peuvent remplacer différents indicateurs techniques pour déterminer la tendance selon leurs besoins.
  • Mise en œuvre actuelle:Utilisez l’EMA (moyenne mobile exponentielle) combinée à l’écart type pour déterminer la tendance
  • Évolutivité:Cette fonction est conçue comme un module enfichable et les utilisateurs peuvent facilement la remplacer par d’autres indicateurs techniques, tels que :
    • RSI (indice de force relative):Évaluer les conditions de surachat et de survente
    • MACD (convergence-divergence des moyennes mobiles): Identifier les points de retournement de tendance
    • Bandes de BollingerJugement de tendance basé sur la volatilité des prix
    • Indicateur KDJ:Jugement combiné de l’élan et de la tendance
    • Portfolio à plusieurs indicateurs:Plusieurs indicateurs peuvent être combinés pour un jugement de tendance plus précis
  • Les étapes
    1. Calculez l’indicateur EMA et déterminez si le prix le franchit.
    2. Déterminez si une tendance se dessine en fonction de l’écart type.
    3. Renvoie la tendance actuelle (longue, courte ou plage).

fonction stopLoss

  • Fonctionnalités:Exécutez l’opération stop-loss conformément aux règles stop-loss.
  • Les étapes
    1. Vérifiez si la position atteint les conditions de stop loss.
    2. Si elle est atteinte, la position sera fermée et les informations de stop loss seront enregistrées.

fonction takeProfit

  • Fonctionnalités:Exécutez l’opération de prise de bénéfices conformément aux règles de prise de bénéfices.
  • Les étapes
    1. Vérifiez si la position répond aux conditions de take-profit.
    2. Si elle est atteinte, la position sera fermée et les informations de take-profit seront enregistrées.

fonction de suiviTakeProfit

  • Fonctionnalités: Exécutez l’opération de prise de profit conformément aux règles de prise de profit de rappel.
  • Les étapes
    1. Vérifiez si la position répond aux conditions de déclenchement du take profit de rappel.
    2. Si elle est atteinte, la position sera fermée et les informations de take-profit seront enregistrées.

fonction d’ordre

  • Fonctionnalités: Exécuter l’opération de commande en fonction des résultats du jugement de tendance.
  • Les étapes
    1. Vérifiez les positions actuelles.
    2. Ouvrez ou fermez des positions en fonction des résultats du jugement de tendance.

Logique de base de la stratégie

Fonction trendStrategy

  • Fonctionnalités:La fonction logique principale de la stratégie, responsable de l’exécution du jugement de tendance, du stop loss et du take profit, du rappel du take profit et des opérations de commande.
  • Les étapes
    1. Obtenir des données de marché:Obtenez des informations actuelles sur le marché, des informations sur les positions, des informations sur les comptes et des données K-line.
    2. Vérifier les positions: Assurez-vous qu’aucune position longue ou courte n’est détenue simultanément, sinon une exception est levée.
    3. Interaction stratégique: Traite les commandes envoyées par les utilisateurs via l’interface interactive.
    4. Impression des informations de la barre d’état: Mettre à jour et imprimer l’état des opérations de stratégie, les informations du compte et l’état de la position.
    5. Arrêt des pertes:Vérifiez et exécutez les opérations de stop-loss conformément aux règles de stop-loss.
    6. Arrêtez de pleurer:Vérifiez et exécutez les opérations de take-profit conformément aux règles de take-profit.
    7. Prendre des bénéfices sur rappel: Vérifiez et exécutez les opérations de take-profit conformément aux règles de take-profit de rappel.
    8. Vérification du cycle de la ligne K: Assurez-vous que la logique de la stratégie est exécutée conformément au cycle de la ligne K.
    9. Le jugement des tendances:Déterminer la tendance actuelle (longue, courte ou oscillante) en fonction des indicateurs techniques.
    10. Passer une commande:Ouvrez ou fermez une position en fonction des résultats du jugement de tendance.

Surveillance de l’état et sortie du journal

fonction printLogStatus

  • Fonctionnalités:Imprimez l’état de l’opération de stratégie, les informations du compte et l’état de la position.
  • Les étapes
    1. Créez des données tabulaires sur l’aperçu de la stratégie, les fonds du compte et les positions.
    2. utiliserLogStatusLa fonction affiche les données du tableau dans la barre d’état.

Fonction principale et exécution de la stratégie

fonction principale

  • Fonctionnalités:La fonction principale de la stratégie, responsable de l’initialisation de la stratégie et du bouclage de la logique de la stratégie.
  • Les étapes
    1. Initialiser l’environnement de simulation d’échange.
    2. Créez une instance de stratégie et initialisez les données.
    3. La logique de la stratégie est exécutée en boucle, en vérifiant la situation du marché et en exécutant des opérations de trading à intervalles réguliers.

Fonctionnalités du framework

  1. Jugement de tendance flexibleEn utilisant l’EMA et l’écart type, cette stratégie permet de déterminer avec souplesse les tendances du marché et s’adapte à divers environnements de marché. Cette fonction n’est qu’un exemple ; les utilisateurs peuvent utiliser différents indicateurs techniques (tels que le RSI, le MACD, les bandes de Bollinger, etc.) pour déterminer les tendances selon leurs besoins.
  2. Divers mécanismes de stop-loss et de take-profit: Prend en charge le stop loss à pourcentage fixe, le take profit et le callback take profit pour répondre aux besoins des traders ayant différentes préférences de risque.
  3. Gestion des données locales: Les données d’opération de politique et les données utilisateur sont enregistrées localement pour garantir que la politique peut être restaurée à son état précédent après le redémarrage.
  4. Commandes interactives: Prend en charge l’interaction avec les politiques via la ligne de commande, ce qui permet aux utilisateurs d’ajuster plus facilement les paramètres de politique ou d’effectuer des opérations spécifiques.

Applicabilité

Ce cadre n’est pas seulement applicable au marché des devises numériques, mais peut également être utilisé dans letrendJudgmentLe cadre peut être étendu pour s’adapter aux différentes exigences des stratégies de trading. Il peut également être adapté spécifiquement au marché au comptant ou aux contrats multivariés, offrant ainsi une grande flexibilité et une grande évolutivité.

  1. Soutien du marché spot:Actuellement, ce cadre est principalement destiné au marché des contrats et, à l’avenir, il pourra être étendu pour prendre en charge les stratégies de trading sur le marché au comptant.
  2. Contrats multi-produits:En ajoutant la prise en charge de plusieurs contrats, les stratégies peuvent surveiller et échanger plusieurs devises numériques simultanément, améliorant ainsi l’utilisation du capital.
  3. Intégration de l’apprentissage automatique:Combiné à des algorithmes d’apprentissage automatique, il améliore encore la précision du jugement des tendances et le niveau d’intelligence des stratégies.
  4. Optimisation de la gestion des risques:Optimiser davantage les mécanismes de gestion des risques, tels que l’ajustement dynamique des ratios de levier, les mécanismes de stop-loss et de take-profit à plusieurs niveaux, pour améliorer la robustesse de la stratégie.

Résumer

Système de trading automatisé complet et hautement flexible, ce framework est adapté au trading de tendance sur le marché des cryptomonnaies. Grâce à une optimisation et une expansion continues, il devrait devenir un outil précieux pour les traders de cryptomonnaies à l’avenir, les aidant à mieux développer leurs propres stratégies quantitatives. Le « Cryptocurrency Trend Strategy Trading Framework » bénéficie d’une structure complète. Bien que relativement volumineux en termes de code, il couvre l’essentiel des modules fonctionnels essentiels au trading de tendance dans une perspective de trading réel. Par conséquent, ce framework présente une valeur de référence significative et une importance pratique, tant pour l’apprentissage des stratégies de trading que pour leur application pratique. Ses fonctionnalités complètes et sa flexibilité lui permettent de s’adapter à divers environnements de marché et d’offrir un support solide.

La plateforme Inventor est une mine d’or de connaissances et de stratégies de trading quantitatif, chacune incarnant la sagesse et l’expérience de ses développeurs. Nous invitons chacun à explorer ici des stratégies et techniques de trading précieuses. Merci à tous nos utilisateurs, créatifs et partageurs. C’est grâce à vos contributions que cette plateforme est devenue un lieu incontournable d’apprentissage et d’échange en trading quantitatif, permettant à chacun d’améliorer ses compétences et son expertise.

”`python “‘backtest start: 2024-11-26 00:00:00 end: 2024-12-03 00:00:00 period: 1d basePeriod: 1d exchanges: [{“eid”:“Futures_Binance”,“currency”:“BTC_USDT”}] “’

import json, talib import numpy as np

class TrendStrategy: def init(self): # 基本设置 self._Currency = TradeCurrency self._Interval = Interval self._UseQuarter = UseQuarter self._UseContract = TradeCurrency + (‘.swap’ if self._UseQuarter else ‘.quarter’) self._OnlyTrendJudgment = OnlyTrendJudgment self._EnableMessageSend = EnableMessageSend # 趋势判断 self._RunInKLinePeriod = RunInKLinePeriod self._KLinePeriod = KLinePeriod self._EmaLength = EmaLength self._EmaCoefficient = EmaCoefficient self._UseStddev = UseStddev self._UseRecordsMiddleValue = UseRecordsMiddleValue self._StddevLength = StddevLength self._StddevDeviations = StddevDeviations # 下单设置 self._MarginLevel = MarginLevel self._OrderSize = OrderSize self._OrderByMargin = OrderByMargin self._OrderMarginPercent = OrderMarginPercent self._PricePrecision = None self._AmountPrecision = None self._OneSizeInCurrentCoin = None self._QuarterOneSizeValue = None # 止盈止损 self._UseStopLoss = UseStopLoss self._StopLossPercent = StopLossPercent self._UseTakeProfit = UseTakeProfit self._TakeProfitPercent = TakeProfitPercent self._UseTrackingTakeProfit = UseTrackingTakeProfit self._UsePositionRetracement = UsePositionRetracement self._TakeProfitTriggerPercent = TakeProfitTriggerPercent self._CallBakcPercent = CallBakcPercent

    # 策略变量
    self._LastBarTime = 0
    self._TrendWhenTakeProfitOrStopLoss = 0
    self._HadStopLoss = False
    self._TriggeredTakeProfit = False
    self._PeakPriceInPosition = 0
    self._HadTakeProfit = False
    self._PriceCrossEMAStatus = 0

    # 统计变量
    self._InitAsset = 0
    self._ProfitLocal = 0
    self._TakeProfitCount = 0
    self._TradeCount = 0
    self.StrategyRunTimeStampString = "strategy_run_time"
    self._StrategyDatas = {"start_run_timestamp": 0, "others": ""}
    self._UserDatas = None

    # 相对固定参数
    self._MaintenanceMarginRate = 0.004
    self._TakerFee = 0.0005
    self._IsUsdtStandard = False

    # 获取合约信息
    ticker = _C(exchange.GetTicker, self._UseContract)
    marketInfo = exchange.GetMarkets()[self._UseContract]
    Log('获取市场信息:', marketInfo)
    self._PricePrecision = marketInfo['PricePrecision']
    self._AmountPrecision = marketInfo['AmountPrecision']
    self._OneSizeInCurrentCoin = marketInfo['CtVal']
    self._QuarterOneSizeValue = marketInfo['CtVal']

    exchange.SetCurrency(self._Currency)
    exchange.SetMarginLevel(self._UseContract, self._MarginLevel)
    exchange.SetPrecision(self._PricePrecision, self._AmountPrecision)

    # 初始化数据
def initDatas(self):

    self.saveStrategyRunTime()
    self.readUserDataLocal()

    self._InitAsset = self._UserDatas["init_assets"]
    self._ProfitLocal = self._UserDatas["profit_local"]
    self._TakeProfitCount = self._UserDatas["take_profit_count"]
    self._TradeCount = self._UserDatas["trade_count"]

    if self._OrderByMargin:
        self.getRealOrderSize(-1, self._OrderSize)
        Log("已经重新计算下单张数:", self._OrderSize)
    if self._UseTakeProfit and self._UseTrackingTakeProfit:
        raise Exception("止盈和回调止盈不能同时使用!")

# 设置合约
def setContract(self):
    self._IsUsdtStandard = "USDT" in self._Currency

    exchange.SetCurrency(self._Currency)
    if self._UseQuarter:
        exchange.SetContractType("quarter")
    else:
        exchange.SetContractType("swap")

# 保存程序起始运行时间 秒级时间戳
def saveStrategyRunTime(self):
    local_data_strategy_run_time = _G(self.StrategyRunTimeStampString)

    if local_data_strategy_run_time is None:
        self._StrategyDatas["start_run_timestamp"] = Unix()
        _G(self.StrategyRunTimeStampString, self._StrategyDatas["start_run_timestamp"])
    else:
        self._StrategyDatas["start_run_timestamp"] = local_data_strategy_run_time

# 设置程序起始运行时间 秒级时间戳
def setStrategyRunTime(self, timestamp):
    _G(self.StrategyRunTimeStampString, timestamp)
    self._StrategyDatas["start_run_timestamp"] = timestamp

# 计算两个时间戳之间的天数,参数是秒级时间戳
def getDaysFromTimeStamp(self, start_time, end_time):
    if end_time < start_time:
        return 0

    return (end_time - start_time) // (60 * 60 * 24)

# 保存数据到本地
def saveUserDatasLocal(self):
    self._UserDatas = {
        "init_assets": self._InitAsset,
        "profit_local": self._ProfitLocal,
        "take_profit_count": self._TakeProfitCount,
        "trade_count": self._TradeCount
    }
    # 存储到本地
    _G(exchange.GetLabel(), self._UserDatas)
    Log("已把所有数据保存到本地.")

# 读取用户本地数据,程序启动时候运行一次
def readUserDataLocal(self):
    user_data = _G(exchange.GetLabel())
    if user_data is None:
        self._InitAsset = self.getAccountAsset(_C(exchange.GetPosition), _C(exchange.GetAccount), _C(exchange.GetTicker))
        self._UserDatas = {
            "init_assets": self._InitAsset,
            "profit_local": 0,
            "take_profit_count": 0,
            "trade_count": 0
        }
    else:
        self._UserDatas = user_data

# 清除用户本地数据,交互按钮点击运行
def clearUserDataLocal(self):
    _G(exchange.GetLabel(), None)
    Log(exchange.GetLabel(), ":已清除本地数据.")

# 策略交互
def runCmd(self):
    cmd = GetCommand()

    if cmd:
        # 检测交互命令
        Log("接收到的命令:", cmd, "#FF1CAE")
        if cmd.startswith("ClearLocalData:"):
            # 清除本地数据
            self.clearUserDataLocal()
        elif cmd.startswith("SaveLocalData:"):
            # 保存数据到本地
            self.saveUserDatasLocal()
        elif cmd.startswith("ClearLog:"):
            # 清除日志
            log_reserve = cmd.replace("ClearLog:", "")
            LogReset(int(log_reserve))
        elif cmd.startswith("OrderSize:"):
            # 修改下单张数
            if self._OrderByMargin:
                Log("已经使用保证金数量来下单,无法直接修改下单数量!")
            else:
                order_size = int(cmd.replace("OrderSize:", ""))
                self._OrderSize = order_size
                Log("下单张数已经修改为:", self._OrderSize)
        elif cmd.startswith("OrderMarginPercent:"):
            # 修改下单保证金百分比
            if self._OrderByMargin:
                order_margin_percent = float(cmd.replace("OrderMarginPercent:", ""))
                self._OrderMarginPercent = order_margin_percent
                Log("下单保证金百分比:", self._OrderMarginPercent, "%")
            else:
                Log("没有打开根据保证金数量下单,无法修改下单保证金百分比!")

# 交易函数
def orderDirectly(self, distance, price, amount):
    tradeFunc = None

    if amount <= 0:
        raise Exception("设置的参数有误,下单数量已经小于0!")

    if distance == "buy":
        tradeFunc = exchange.Buy
    elif distance == "sell":
        tradeFunc = exchange.Sell
    elif distance == "closebuy":
        tradeFunc = exchange.Sell
    else:
        tradeFunc = exchange.Buy

    exchange.SetDirection(distance)
    return tradeFunc(price, amount)

def openLong(self, price, amount):
    real_amount = self.getRealOrderSize(price, amount)
    return self.orderDirectly("buy", price, real_amount)

def openShort(self, price, amount):
    real_amount = self.getRealOrderSize(price, amount)
    return self.orderDirectly("sell", price, real_amount)

def coverLong(self, price, amount):
    return self.orderDirectly("closebuy", price, amount)

def coverShort(self, price, amount):
    return self.orderDirectly("closesell", price, amount)

# 重新计算下单数量
def getRealOrderSize(self, price, amount):
    real_price = price if price != -1 else _C(exchange.GetTicker).Last
    if self._OrderByMargin:
        if self._IsUsdtStandard:

            self._OrderSize = _N(self._InitAsset * (self._OrderMarginPercent / 100) / real_price * self._MarginLevel / self._OneSizeInCurrentCoin, self._AmountPrecision)  # u本位数量(杠杆放大数量)

        else:
            self._OrderSize = _N(self._InitAsset * (self._OrderMarginPercent / 100) * self._MarginLevel * real_price / self._QuarterOneSizeValue, self._AmountPrecision)  # 币本位数量(杠杆放大数量)
    else:
        self._OrderSize = amount
    return self._OrderSize

# 获取单个持仓占用保证金
def getSinglePositionMargin(self, position, ticker):
    position_margin = 0

    if len(position) > 0:
        if self._IsUsdtStandard:
            position_margin = position[0].Amount * self._OneSizeInCurrentCoin * ticker.Last / self._MarginLevel
        else:
            position_margin = position[0].Amount * self._QuarterOneSizeValue / ticker.Last / self._MarginLevel

    return position_margin

# 获取单向持仓的收益和收益%
def getSinglePositionProfit(self, position, ticker):
    if len(position) == 0:
        return [0, 0]

    price = ticker.Last
    position_margin = self.getSinglePositionMargin(position, ticker)

    position_profit_percent = (price - position[0].Price) / position[0].Price * self._MarginLevel if position[0].Type == PD_LONG else (position[0].Price - price) / position[0].Price * self._MarginLevel
    position_profit = position_margin * position_profit_percent

    return [position_profit, position_profit_percent]

# 计算强平价格
def calculateForcedPrice(self, account, position, ticker):
    position_profit = 0
    total_avail_balance = 0
    forced_price = 0

    position_margin = self.getSinglePositionMargin(position, ticker)
    [position_profit, position_profit_percent] = self.getSinglePositionProfit(position, ticker)

    if self._IsUsdtStandard:
        total_avail_balance = account.Balance + position_margin + account.FrozenBalance - position_profit if position_profit > 0 else account.Balance + position_margin + account.FrozenBalance
        if position[0].Type == PD_LONG:
            forced_price = ((self._MaintenanceMarginRate + self._TakerFee) * self._MarginLevel * account.FrozenBalance - total_avail_balance) / self._OneSizeInCurrentCoin + (position[0].Amount * position[0].Price) / (position[0].Amount - (self._MaintenanceMarginRate + self._TakerFee) * position[0].Amount)
        else:
            forced_price = ((self._MaintenanceMarginRate + self._TakerFee) * self._MarginLevel * account.FrozenBalance - total_avail_balance) / self._OneSizeInCurrentCoin - (position[0].Amount * position[0].Price) / (-1 * position[0].Amount - (self._MaintenanceMarginRate + self._TakerFee) * position[0].Amount)
    else:
        total_avail_balance = account.Stocks + position_margin + account.FrozenStocks - position_profit if position_profit > 0 else account.Stocks + position_margin + account.FrozenStocks
        if position[0].Type == PD_LONG:
            forced_price = (self._MaintenanceMarginRate * position[0].Amount + position[0].Amount) / (total_avail_balance / self._QuarterOneSizeValue + position[0].Amount / position[0].Price)
        else:
            forced_price = (self._MaintenanceMarginRate * position[0].Amount - position[0].Amount) / (total_avail_balance / self._QuarterOneSizeValue - position[0].Amount / position[0].Price)

    if forced_price < 0:
        forced_price = 0

    return forced_price

# 计算最大可下单张数
def getMaxOrderSize(self, margin_level, ticker, account):
    max_order_size = 0

    if self._IsUsdtStandard:
        max_order_size = account.Balance * margin_level / (self._OneSizeInCurrentCoin * ticker.Last)
    else:
        max_order_size = account.Stocks * ticker.Last / self._QuarterOneSizeValue * margin_level

    return _N(max_order_size, self._AmountPrecision)

# 获取账户资产
def getAccountAsset(self, position, account, ticker):
    # 计算不同情况下的账户初始资产
    account_asset = 0
    position_margin = self.getSinglePositionMargin(position, ticker)

    if self._IsUsdtStandard:
        if len(position) > 0:
            account_asset = account.Balance + account.FrozenBalance + position_margin
        else:
            account_asset = account.Balance + account.FrozenBalance
    else:
        if len(position) > 0:
            account_asset = account.Stocks + account.FrozenStocks + position_margin
        else:
            account_asset = account.Stocks + account.FrozenStocks

    return account_asset

# 收益统计
def calculateProfit(self, ticker):
    # 重新获取一下账户持仓与资产
    position = _C(exchange.GetPosition)
    account = _C(exchange.GetAccount)
    # 当前总收益 - 上一次总收益 = 本次的收益
    current_profit = (self.getAccountAsset(position, account, ticker) - self._InitAsset) - self._ProfitLocal
    self._ProfitLocal += current_profit

    if current_profit > 0:
        self._TakeProfitCount += 1
    self._TradeCount += 1

    LogProfit(_N(self._ProfitLocal, 4), "        本次收益:", _N(current_profit, 6))
    self.saveUserDatasLocal()

# 是否还够资金下单
def isEnoughAssetToOrder(self, order_size, ticker):
    is_enough = True
    account = _C(exchange.GetAccount)

    if self._IsUsdtStandard:
        if account.Balance < order_size * ticker.Last * self._OneSizeInCurrentCoin / self._MarginLevel:
            is_enough = False
    else:
        if account.Stocks < order_size * self._QuarterOneSizeValue / ticker.Last / self._MarginLevel:
            is_enough = False

    return is_enough

# 按照K线周期运行策略核心
def runInKLinePeriod(self, records):
    bar_time = records[-1].Time
    if self._RunInKLinePeriod and self._LastBarTime == bar_time:
        return False

    self._LastBarTime = bar_time
    return True

# 趋势判断模块(可编辑具体指标)
def trendJudgment(self, records):
    # 检查价格是否穿过均线
    def checkPriceCrossEma(price, ema_value):
        if self._PriceCrossEMAStatus == 0:
            if price <= ema_value:
                self._PriceCrossEMAStatus = -1
            else:
                self._PriceCrossEMAStatus = 1
        elif (self._PriceCrossEMAStatus == -1 and price >= ema_value) or (self._PriceCrossEMAStatus == 1 and price <= ema_value):
            self._PriceCrossEMAStatus = 2  # 完成穿过

    # EMA的多空判断
    ema_long = False
    ema_short = False
    price = records[-2].Close  # 已经收盘的K线的收盘价格
    ema = TA.EMA(records, self._EmaLength)
    ema_value = ema[-2]  # 收盘K线对应ema值
    ema_upper = ema_value * (1 + self._EmaCoefficient)
    ema_lower = ema_value * (1 - self._EmaCoefficient)

    checkPriceCrossEma(price, ema_value)
    if price > ema_upper:
        ema_long = True
    elif price < ema_lower:
        ema_short = True

    # 标准差判断
    in_trend = False
    if self._UseStddev:
        records_data = []
        for i in range(len(records)):
            records_data.append((records[i].High + records[i].Low) / 2 if self._UseRecordsMiddleValue else records[i].Close)

        records_data = np.array(records_data)  # 将 list 转换为 np.array
        stddev = np.std(records_data, ddof=1)  # 使用 numpy 计算标准差
        if stddev > self._StddevDeviations:
            in_trend = True
    else:
        in_trend = True

    # 趋势判断
    long = in_trend and ema_long 
    short = in_trend and ema_short

    if long:
        Log("当前趋势为:多", self._EnableMessageSend and "@" or "#00FF7F")
    elif short:
        Log("当前趋势为:空", self._EnableMessageSend and "@" or "#FF0000")
    else:
        Log("当前趋势为:震荡", self._EnableMessageSend and "@" or "#007FFF")

    return [long, short]

# 止损
def stopLoss(self, position, ticker):
    stop_loss_price = 0
    price = ticker.Last

    if len(position) == 1 and self._UseStopLoss:
        if position[0].Type == PD_LONG:
            stop_loss_price = position[0].Price * (1 - self._StopLossPercent / 100)
            if price < stop_loss_price:
                self.coverLong(-1, position[0].Amount)
                self.calculateProfit(ticker)
                self._TrendWhenTakeProfitOrStopLoss = 1
                self._HadStopLoss = True
                Log("多单止损。止损价格:", _N(stop_loss_price, 6), ", 持仓价格:", _N(position[0].Price), self._EnableMessageSend and "@" or "#FF1CAE")
        elif position[0].Type == PD_SHORT:
            stop_loss_price = position[0].Price * (1 + self._StopLossPercent / 100)
            if price > stop_loss_price:
                self.coverShort(-1, position[0].Amount)
                self.calculateProfit(ticker)
                self._TrendWhenTakeProfitOrStopLoss = -1
                self._HadStopLoss = True
                Log("空单止损。止损价格:", _N(stop_loss_price, 6), ", 持仓价格:", _N(position[0].Price), self._EnableMessageSend and "@" or "#FF1CAE")

# 止盈
def takeProfit(self, position, ticker):
    take_profit_price = 0
    price = ticker.Last

    if len(position) == 1 and self._UseTakeProfit:
        if position[0].Type == PD_LONG:
            take_profit_price = position[0].Price * (1 + self._TakeProfitPercent / 100)
            if price > take_profit_price:
                self.coverLong(-1, position[0].Amount)
                self.calculateProfit(ticker)
                self._TrendWhenTakeProfitOrStopLoss = 1
                self._HadTakeProfit = True
                Log("多单止盈。止盈价格:", _N(take_profit_price, 6), ", 持仓价格:", _N(position[0].Price), self._EnableMessageSend and "@" or "#FF1CAE")
        elif position[0].Type == PD_SHORT:
            take_profit_price = position[0].Price * (1 - self._TakeProfitPercent / 100)
            if price < take_profit_price:
                self.coverShort(-1, position[0].Amount)
                self.calculateProfit(ticker)
                self._TrendWhenTakeProfitOrStopLoss = -1
                self._HadTakeProfit = True
                Log("空单止盈。止盈价格:", _N(take_profit_price, 6), ", 持仓价格:", _N(position[0].Price), self._EnableMessageSend and "@" or "#FF1CAE")

# 回调止盈
def trackingTakeProfit(self, position, ticker):
    take_profit_price = 0
    trigger_price = 0
    price = ticker.Last

    if len(position) > 0 and self._UseTrackingTakeProfit:
        if position[0].Type == PD_LONG:
            # 多单持仓
            if self._TriggeredTakeProfit:
                # 已达到触发价格,监控是否止盈
                self._PeakPriceInPosition = price if price > self._PeakPriceInPosition else self._PeakPriceInPosition  # 更新价格高点
                if self._UsePositionRetracement:
                    take_profit_price = self._PeakPriceInPosition - (self._PeakPriceInPosition - position[0].Price) * (self._CallBakcPercent / 100)  # 计算回调的止盈价格
                else:
                    take_profit_price = self._PeakPriceInPosition * (1 - self._CallBakcPercent / 100)  # 计算回调的止盈价格
                if price < take_profit_price:
                    self.coverLong(-1, position[0].Amount)  # 平多
                    self.calculateProfit(ticker)
                    self._TriggeredTakeProfit = False  # 复位触发标记
                    self._TrendWhenTakeProfitOrStopLoss = 1  # 记录止盈时候的趋势
                    self._HadTakeProfit = True  # 记录发生了止盈
                    Log("多单回调止盈:持仓中价格高点:", _N(self._PeakPriceInPosition, 6), ", 止盈价格:", _N(take_profit_price, 6), ", 当前价格:", _N(price, 6),
                        ", 持仓价格:", _N(position[0].Price, 6), self._EnableMessageSend and "@" or "#FF1CAE")
            else:
                # 监控是否达到回调止盈的触发价格
                trigger_price = position[0].Price * (1 + self._TakeProfitTriggerPercent / 100)
                if price > trigger_price:
                    self._TriggeredTakeProfit = True  # 触发回调止盈
                    self._PeakPriceInPosition = price  # 记录价格高点
                    Log("多单已达到回调止盈的触发价格:", _N(trigger_price, 6), ", 当前价格:", _N(price, 6), ", 持仓价格:", _N(position[0].Price, 6))
        elif position[0].Type == PD_SHORT:
            # 空单持仓
            if self._TriggeredTakeProfit:
                # 已达到触发价格,监控是否止盈
                self._PeakPriceInPosition = price if price < self._PeakPriceInPosition else self._PeakPriceInPosition  # 更新价格低点
                if self._UsePositionRetracement:
                    take_profit_price = self._PeakPriceInPosition + (position[0].Price - self._PeakPriceInPosition) * (self._CallBakcPercent / 100)  # 计算回调的止盈价格
                else:
                    take_profit_price = self._PeakPriceInPosition * (1 + self._CallBakcPercent / 100)  # 计算回调的止盈价格
                if price > take_profit_price:
                    self.coverShort(-1, position[0].Amount)  # 平空
                    self.calculateProfit(ticker)
                    self._TriggeredTakeProfit = False  # 复位触发标记
                    self._TrendWhenTakeProfitOrStopLoss = -1  # 记录止盈时候的趋势
                    self._HadTakeProfit = True  # 记录发生了止盈
                    Log("空单回调止盈:持仓中价格低点:", _N(self._PeakPriceInPosition, 6), ", 止盈价格:", _N(take_profit_price, 6), ", 当前价格:", _N(price, 6),
                        ", 持仓价格:", _N(position[0].Price, 6), self._EnableMessageSend and "@" or "#FF1CAE")
            else:
                # 监控是否达到回调止盈的触发价格
                trigger_price = position[0].Price * (1 - self._TakeProfitTriggerPercent / 100)
                if price < trigger_price:
                    self._TriggeredTakeProfit = True  # 触发回调止盈
                    self._PeakPriceInPosition = price  # 记录价格低点
                    Log("空单已达到回调止盈的触发价格:", _N(trigger_price, 6), ", 当前价格:", _N(price, 6), ", 持仓价格:", _N(position[0].Price, 6))

# 下单
def order(self, long, short, position, ticker):
    position_size = position[0].Amount if len(position) > 0 else 0
    position_type = position[0].Type if len(position) > 0 else None

    if long:
        # 趋势多
        if (self._HadStopLoss or self._HadTakeProfit) and self._TrendWhenTakeProfitOrStopLoss == 1:
            # 发生了止盈止损,并且止盈止损时候趋势为多,不再做多
            return
        if position_size > 0 and position_type == PD_SHORT:
            self.coverShort(-1, position_size)
            self.calculateProfit(ticker)
        elif position_size > 0 and position_type == PD_LONG:
            # 多单持仓,不重复下单
            return
        else:
            # 没有持仓,如果是首次运行或者策略重启,需要等待价格穿过一次EMA均线才下单
            if self._PriceCrossEMAStatus != 2:
                return
        if self.isEnoughAssetToOrder(self._OrderSize, ticker):
            self.openLong(-1, self._OrderSize)
            self._HadStopLoss = False
            self._HadTakeProfit = False
        else:
            raise Exception("下单金额数量不足!")
    elif short:
        # 趋势空

        if (self._HadStopLoss or self._HadTakeProfit) and self._TrendWhenTakeProfitOrStopLoss == -1:
            # 发生了止盈止损,并且止盈止损时候趋势为空,不再做空
            return

        if position_size > 0 and position_type == PD_LONG:
            self.coverLong(-1, position_size)
            self.calculateProfit(ticker)
        elif position_size > 0 and position_type == PD_SHORT:
            # 空单持仓,不重复下单
            return
        else:
            # 没有持仓,如果是首次运行或者策略重启,需要等待价格穿过一次EMA均线才下单
            if self._PriceCrossEMAStatus != 2:
                return

        if self.isEnoughAssetToOrder(self._OrderSize, ticker):
            self.openShort(-1, self._OrderSize)
            self._HadStopLoss = False
            self._HadTakeProfit = False
        else:
            raise Exception("下单金额数量不足!")

# 趋势策略
def trendStrategy(self):
    ticker = _C(exchange.GetTicker)
    position = _C(exchange.GetPosition)
    account = _C(exchange.GetAccount)
    records = _C(exchange.GetRecords, self._KLinePeriod * 60)
    if len(position) > 1:
        Log(position)
        raise Exception("同时有多空持仓!")
    # 策略交互
    self.runCmd()
    # 状态栏信息打印
    self.printLogStatus(ticker, account, position)
    # 止损
    self.stopLoss(position, ticker)
    # 止盈
    self.takeProfit(position, ticker)
    # 回调止盈
    self.trackingTakeProfit(position, ticker)

    # 按照K线周期运行策略
    if not self.runInKLinePeriod(records):
        return
    # 趋势判断和下单
    long = False
    short = False
    [long, short] = self.trendJudgment(records)
    if not self._OnlyTrendJudgment:
        self.order(long, short, position, ticker)

# 状态栏信息打印
def printLogStatus(self, ticker, account, position):
    table_overview = {
        "type": "table",
        "title": "策略总览",
        "cols": ["开始时间", "已运行天数", "交易次数", "胜率", "预估月化%", "预估年化%"],
        "rows": []
    }
    table_account = {
        "type": "table",
        "title": "账户资金",
        "cols": ["当前资产", "初始资产", "可用余额", "冻结余额", "可下单张数", "收益", "收益%"],
        "rows": []
    }
    table_position = {
        "type": "table",
        "title": "持仓情况",
        "cols": ["交易币种", "杠杆倍数", "持仓均价", "方向", "数量", "保证金", "预估强平价格", "浮动盈亏", "浮动盈亏%"],
        "rows": []
    }
    i = 0

    # 策略总览
    the_running_days = self.getDaysFromTimeStamp(self._StrategyDatas["start_run_timestamp"], Unix())
    monthly_rate_of_profit = 0
    if the_running_days > 1:
        monthly_rate_of_profit = self._ProfitLocal / self._InitAsset / the_running_days * 30
    table_overview["rows"].append([_D(self._StrategyDatas["start_run_timestamp"]), the_running_days, self._TradeCount,
                                   0 if self._TradeCount == 0 else (str(_N(self._TakeProfitCount / self._TradeCount * 100, 2)) + "%"),
                                   str(_N(monthly_rate_of_profit * 100, 2)) + "%", str(_N(monthly_rate_of_profit * 12 * 100, 2)) + "%"])
    # 账户资金
    current_asset = self.getAccountAsset(position, account, ticker)
    max_order_size = self.getMaxOrderSize(self._MarginLevel, ticker, account)
    asset_profit = current_asset - self._InitAsset
    asset_profit_percent = asset_profit / self._InitAsset
    table_account["rows"].append([_N(current_asset, 4), _N(self._InitAsset, 4), _N(account.Balance if self._IsUsdtStandard else account.Stocks, 4),
                                  _N(account.FrozenBalance if self._IsUsdtStandard else account.FrozenStocks, 4), max_order_size, _N(asset_profit, 4),
                                  str(_N(asset_profit_percent * 100, 2)) + "%"])
    # 持仓情况