2
フォロー
319
フォロワー

Inventor Platform Gold Rush: 柔軟性の高い Python トレンド取引フレームワークの実践的分析

作成日:: 2025-08-22 17:25:52, 更新日:: 2025-08-26 09:56:27
comments   0
hits   468

私はよく発明家のプラットフォームにいて、いつも宝物を見つけます。今日は21歳のトレンド戦略、私は原著者の精巧で完璧なコード構造に感心しており、その柔軟性も高いです。オリジナルの戦略はJS版で、Python愛好家の利便性のために書き直されています。

正直なところ、多くの初心者はクオンツトレードを始める際に、多くの回り道をしてしまいます。注文の失敗、リスク管理の不備による損失、戦略再開後のデータ損失といった問題にしばしば遭遇します。その後、私は徐々に、多くの落とし穴を回避するのに役立つ優れたフレームワークの重要性に気づきました。このトレンド戦略フレームワークは非常に貴重なツールです。単なるシンプルな取引戦略ではなく、注文の発注、損切り注文、データ管理といった基本的でありながら重要な機能を提供するツールボックスのようなものです。「いつ買うべきか」「いつ売るべきか」という核心的な問題に集中するだけで済みます。さらに、このフレームワークは非常にオープンエンドで、EMAをMACD、RSI、その他お好みの指標に簡単に置き換えることができます。トレンドを追跡したいですか?問題ありません。平均回帰を試してみたいですか?複数の指標を組み合わせたいですか?もちろんです。この柔軟性は非常に便利です。同じコードを変更して、さまざまなアイデアを試すことができます。

本日はこのフレームワークを共有し、クオンツ投資を検討されている方々のお役に立てれば幸いです。以下では、このフレームワークの各構成要素について詳しくご紹介します。きっとお役に立てると思います。

フレームワークの構造と機能

マルチ商品取引フレームワークで使用される複数の独立した関数と比較して、このフレームワークはクラス形式を用いて戦略の様々な部分を整理・管理しようとします。このオブジェクト指向設計は、コードの保守性と拡張性を向上させるだけでなく、戦略コンポーネントのモジュール化を促進し、後続の調整や最適化を容易にします。このフレームワークは主に以下のセクションで構成され、それぞれが独自の機能を持ち、戦略の柔軟性と実用性を確保しています。

初期化とセットアップ

init関数

  • 機能__init__この関数は戦略クラスの初期化メソッドであり、戦略の基本設定、変数の初期化、市場情報の取得を担います。この関数により、戦略実行前に必要なパラメータが設定され、その後の取引操作がスムーズに実行されるようになります。
  • ステップ
    1. 基本構成: 取引通貨、契約タイプ、利益確定および損切りルールなどを設定します。
    2. 市場情報:契約の価格精度と数量の正確性を取得し、注文の合法性を確保します。
    3. 変数の初期化: トレンド判断、利益確定と損失確定のパラメータ、統計変数などが含まれており、市場の状況に基づいて戦略を決定するのに役立ちます。
    4. Exchange設定:マージン設定、精度など、市場情報に応じて取引所APIインターフェースを設定します。

initDatas関数

  • 機能: アカウント資産、利益統計など、戦略実行時のデータを初期化します。
  • ステップ
    1. ポリシー実行時間を節約します。
    2. ローカルユーザーデータを読み取ります。
    3. アカウント資産、収入統計、その他のデータを初期化します。
    4. テイクプロフィットとコールバック テイクプロフィットが同時に有効になっているかどうかを確認します。

データ管理と保存

saveStrategyRunTime関数

  • 機能: 後続の統計および監視のためにポリシーの開始時刻を保存します。
  • ステップ
    1. 実行時間がローカルに保存されているかどうかを確認します。
    2. 保存されていない場合は、現在の時刻を記録してローカルに保存します。
    3. 保存されている場合は、ローカルに保存された時間を読み取ります。

setStrategyRunTime関数

  • 機能: ポリシーの開始時刻を設定し、ローカル ストレージに保存します。
  • ステップ
    1. プラットフォームの使用_Gこの関数は渡されたタイムスタンプをローカルに保存します。
    2. ポリシー データ内の開始実行時刻を更新します。

getDaysFromTimeStamp関数

  • 機能: 2 つのタイムスタンプ間の日数の差を計算して、ポリシー実行期間を計算します。
  • ステップ
    1. 終了時刻が開始時刻より早いかどうかを確認し、早い場合は 0 を返します。
    2. 2 つのタイムスタンプ間の秒数の差を計算し、それを日数に変換します。
    3. 日数の差を返します。

saveUserDatasLocal関数

  • 機能: ポリシー実行中にキー データをローカル コンピューターに保存し、ポリシーの再起動時に復元できるようにします。
  • ステップ
    1. アカウント資産や収入統計などのデータをパッケージ化します。
    2. プラットフォームの使用_Gこの関数はデータをローカルに保存します。

readUserDataLocal関数

  • 機能: ポリシーの再起動時にデータ回復のためにローカルに保存されたユーザー データを読み取ります。
  • ステップ
    1. ローカルに保存されたデータがあるかどうかを確認します。
    2. そうでない場合は、データを初期化してローカルに保存します。
    3. そうであれば、それが読み取られ、ポリシーにロードされます。

clearUserDataLocal関数

  • 機能: ローカルに保存されたユーザー データをクリアします。通常は、ポリシーのリセットまたはデバッグに使用されます。
  • ステップ
    1. プラットフォームの使用_G関数はローカルデータをクリアします。
    2. ログクリア操作。

ポリシーの相互作用とコマンド処理

runCmd関数

  • 機能: ローカルデータのクリア、注文数量の変更など、インタラクティブ インターフェイスを通じてユーザーから送信されたコマンドを処理します。
  • ステップ
    1. ユーザーが送信したコマンドを取得します。
    2. ローカルデータのクリア、注文数量の変更など、コマンドの種類に基づいて対応する操作を実行します。
    3. コマンド実行結果を記録します。

取引と注文管理

orderDirectly関数

  • 機能: 方向と価格に基づいて直接注文を出し、開閉操作をサポートします。
  • ステップ
    1. 方向(買いまたは売り)に応じて取引機能を選択します。
    2. 取引方向を設定します。
    3. 注文操作を実行し、結果を返します。

openLong関数

  • 機能: ロングポジションを開き、価格と数量に基づいて注文を出します。
  • ステップ
    1. 実際の注文数量を計算します。
    2. 電話orderDirectlyこの関数は購入操作を実行します。

openShort関数

  • 機能:ショートポジションを開き、価格と数量に基づいて注文を出します。
  • ステップ
    1. 実際の注文数量を計算します。
    2. 電話orderDirectlyこの関数は売り操作を実行します。

coverLong関数

  • 機能:価格と数量に基づいてロングポジションを決済し、注文を出します。
  • ステップ
    1. 電話orderDirectlyこの関数は売り操作を実行します。

coverShort関数

  • 機能:価格と数量に基づいてショートポジションを決済し、注文を出します。
  • ステップ
    1. 電話orderDirectlyこの関数は購入操作を実行します。

getRealOrderSize関数

  • 機能: 価格と数量に基づいて実際の注文数量を再計算し、マージン率に基づいて注文を行うことをサポートします。
  • ステップ
    1. マージン率に応じて注文が行われたかどうかに基づいて実際の注文数量を計算します。
    2. 計算された注文数量を返します。

リスク管理とリターン統計

getSinglePositionMargin関数

  • 機能: 1 つのポジションが占めるマージンを計算します。
  • ステップ
    1. マージンはポジションの方向と数量に基づいて計算されます。
    2. 計算結果を返します。

getSinglePositionProfit関数

  • 機能: 単一ポジションの収益と利回りを計算します。
  • ステップ
    1. ポジションの方向と現在の価格に基づいて利益を計算します。
    2. 収益と利回りを返します。

calculateForcedPrice関数

  • 機能: ポジションの清算価格を計算します。
  • ステップ
    1. 清算価格はポジションの方向と口座残高に基づいて計算されます。
    2. 計算結果を返します。

getMaxOrderSize関数

  • 機能: 最大注文数量を計算します。
  • ステップ
    1. 発注できる最大注文数量は、口座残高とレバレッジに基づいて計算されます。
    2. 計算結果を返します。

getAccountAsset関数

  • 機能: ポジションと利用可能な残高を含むアカウント資産の合計を計算します。
  • ステップ
    1. ポジションと口座残高に基づいて総資産を計算します。
    2. 計算結果を返します。

利益計算関数

  • 機能: 戦略のリターンを計算して記録します。
  • ステップ
    1. 現在の総収益と初期資産の差を計算します。
    2. 利益を記録し、統計変数を更新します。
    3. 収入データをローカルに保存します。

isEnoughAssetToOrder関数

  • 機能: 注文を行うためにアカウント資金が十分であるかどうかを確認します。
  • ステップ
    1. 口座残高情報を取得します。
    2. 取引通貨の種類(USDT ベースまたはコイン ベース)に基づいて必要な資金を計算します。
    3. アカウント残高が注文要件を満たしているかどうかを確認します。
    4. 資金が十分かどうかを示すブール値を返します。

トレンド判断と取引ロジック

runInKLinePeriod関数

  • 機能: K ライン サイクルに基づいて戦略ロジックを実行するかどうかを決定します。
  • ステップ
    1. 現在の K ラインが処理されているかどうかを確認します。
    2. 処理されていない場合は、処理済みとしてマークして返しますTrueそれ以外の場合は、False

trendJudgement関数(コアトレンド判定モジュール)

  • 機能テクニカル指標に基づいて現在のトレンドを判断します。これはフレームワーク全体の中で最も柔軟性の高いモジュールです。ユーザーはニーズに合わせて様々なテクニカル指標を置き換え、トレンドを判断することができます。
  • 現在の実装: EMA(指数移動平均)と標準偏差を組み合わせてトレンドを判断する
  • スケーラビリティ:この機能はプラグイン可能なモジュールとして設計されており、ユーザーは次のような他のテクニカル指標と簡単に置き換えることができます。
    • RSI(相対力指数):買われすぎ、売られすぎの判断
    • MACD(移動平均収束拡散): トレンドの転換点を特定する
    • ボリンジャーバンド:価格変動に基づくトレンド判断
    • KDJインジケーター:モメンタムとトレンドの複合判断
    • 多指標組合せ: 複数の指標を組み合わせることで、より正確なトレンド判断が可能になります
  • ステップ
    1. EMA インジケーターを計算し、価格がそれを越えるかどうかを判断します。
    2. 標準偏差に基づいてトレンドかどうかを判断します。
    3. 現在のトレンド (長期、短期、範囲) を返します。

ストップロス関数

  • 機能:損切りルールに従って損切り操作を実行します。
  • ステップ
    1. ポジションがストップロス条件に達しているかどうかを確認します。
    2. 到達するとポジションはクローズされ、ストップロス情報が記録されます。

利益確定関数

  • 機能:利益確定ルールに従って利益確定操作を実行します。
  • ステップ
    1. ポジションが利益確定条件を満たしているかどうかを確認します。
    2. 到達した場合、ポジションはクローズされ、利益確定情報が記録されます。

トラッキングテイクプロフィット関数

  • 機能: コールバック利益確定ルールに従って利益確定操作を実行します。
  • ステップ
    1. ポジションがコールバック利益確定トリガー条件を満たしているかどうかを確認します。
    2. 到達した場合、ポジションはクローズされ、利益確定情報が記録されます。

注文機能

  • 機能: トレンド判定結果に基づいて注文操作を実行します。
  • ステップ
    1. 現在の位置を確認します。
    2. トレンド判断結果に基づいてポジションを開いたり閉じたりします。

戦略コアロジック

トレンド戦略関数

  • 機能: 戦略の中核ロジック機能であり、トレンド判断、損切りと利益確定、利益確定のコールバック、注文操作の実行を担当します。
  • ステップ
    1. 市場データを取得する: 現在の市場情報、ポジション情報、アカウント情報、K ライン データを取得します。
    2. ポジションを確認する: ロングポジションまたはショートポジションが同時に保持されていないことを確認してください。そうでない場合は例外がスローされます。
    3. 戦略のやり取り: 対話型インターフェースを通じてユーザーが送信したコマンドを処理します。
    4. ステータスバー情報の印刷: 戦略運用ステータス、アカウント情報、ポジションステータスを更新して印刷します。
    5. ストップダスト:損切りルールに従って損切り操作を確認し実行します。
    6. ストップ: 利益確定ルールに従って利益確定操作を確認し、実行します。
    7. コールバックで利益確定: コールバック利益確定ルールに従って利益確定操作を確認し、実行します。
    8. Kラインサイクルチェック: 戦略ロジックが K ライン サイクルに従って実行されることを確認します。
    9. 傾向を判断する: テクニカル指標に基づいて現在のトレンド (ロング、ショート、または振動) を判断します。
    10. 注文する: トレンド判定結果に基づいてポジションをオープンまたはクローズします。

ステータス監視とログ出力

printLogStatus関数

  • 機能: 戦略実行状況、アカウント情報、ポジション状況を印刷します。
  • ステップ
    1. 戦略の概要、アカウント資金、ポジションの表形式データを構築します。
    2. 使用LogStatusこの関数はテーブルデータをステータスバーに出力します。

主な機能と戦略実行

メイン関数

  • 機能: 戦略の初期化と戦略ロジックのループ処理を担当する、戦略の主な関数。
  • ステップ
    1. 交換シミュレーション環境を初期化します。
    2. 戦略インスタンスを作成し、データを初期化します。
    3. 戦略ロジックはループで実行され、市場の状況をチェックし、定期的に取引操作を実行します。

フレームワークの機能

  1. 柔軟なトレンド判断: この戦略はEMAと標準偏差を用いて市場のトレンドを柔軟に判断し、様々な市場環境に適用できます。この機能はあくまで一例であり、ユーザーは必要に応じて様々なテクニカル指標(RSI、MACD、ボリンジャーバンドなど)を用いてトレンドを判断することができます。
  2. 多様なストップロスとテイクプロフィットの仕組み:さまざまなリスク選好を持つトレーダーのニーズを満たすために、固定パーセンテージのストップロス、テイクプロフィット、コールバックテイクプロフィットをサポートします。
  3. ローカルデータ管理: ポリシー操作データとユーザー データはローカルに保存され、再起動後にポリシーを以前の状態に復元できるようになります。
  4. 対話型コマンド: コマンドラインを介したポリシーとの対話をサポートし、ユーザーがポリシーパラメータを調整したり、特定の操作を実行したりすることを容易にします。

適用範囲

このフレームワークはデジタル通貨市場に適用できるだけでなく、trendJudgmentこのフレームワークは、様々な取引戦略の要件に合わせて機能を拡張できます。さらに、スポット市場や多品種契約向けにカスタマイズすることもでき、高い柔軟性と拡張性を備えています。

  1. スポット市場サポート現在、このフレームワークは主に契約市場を対象としていますが、将来的にはスポット市場での取引戦略をサポートするように拡張される可能性があります。
  2. 複数製品契約: 複数の契約のサポートを追加することで、戦略は複数のデジタル通貨を同時に監視および取引できるようになり、資本の利用率が向上します。
  3. 機械学習の統合: 機械学習アルゴリズムと組み合わせることで、トレンド判断の精度と戦略のインテリジェンスレベルがさらに向上します。
  4. リスク管理の最適化: レバレッジ比率の動的な調整、多段階のストップロスおよびテイクプロフィットのメカニズムなどのリスク管理メカニズムをさらに最適化し、戦略の堅牢性を向上させます。

要約する

このフレームワークは、包括的かつ柔軟性の高い自動取引システムであり、仮想通貨市場におけるトレンドトレードに適しています。継続的な最適化と拡張により、将来的には仮想通貨トレーダーにとって貴重なツールとなり、独自の定量戦略をより良く構築するのに役立つことが期待されています。「仮想通貨トレンド戦略取引フレームワーク」は包括的な構造を誇ります。コードは比較的大規模ですが、実取引の観点からトレンドトレードに必要なコア機能モジュールを網羅しています。そのため、このフレームワークは、取引戦略の学習と実用化の両面において、重要な参考価値と実用的意義を有しています。その包括的な機能と柔軟性により、多様な市場環境に適応し、強力なサポートを提供します。

Inventorプラットフォームは、開発者の知恵と経験が凝縮された、クオンツ取引に関する知識と戦略の宝庫です。皆様に、このプラットフォームで価値ある取引戦略とテクニックを探求していただければ幸いです。革新的で情報を共有してくださるすべてのユーザーの皆様に感謝申し上げます。皆様の貢献のおかげで、このプラットフォームはクオンツ取引における学習と交流の重要な場となり、皆様のスキルと専門知識の向上に役立っています。

”`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),