avatar of ianzeng123 ianzeng123
집중하다 사신
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 함수

  • 기능: 두 타임스탬프 사이의 일수 차이를 계산하여 정책 실행 기간을 계산합니다.
  • 단계
    1. 종료 시간이 시작 시간보다 이전인지 확인하고, 그렇다면 0을 반환합니다.
    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. 마진은 위치 방향과 수량에 따라 계산됩니다.
    2. 계산 결과를 반환합니다.

getSinglePositionProfit 함수

  • 기능: 단일 포지션의 수입과 수익률을 계산합니다.
  • 단계
    1. 포지션 방향과 현재 가격을 기준으로 이익을 계산합니다.
    2. 수입과 수익률을 반환합니다.

calculateForcedPrice 함수

  • 기능: 포지션의 청산 가격을 계산합니다.
  • 단계
    1. 청산 가격은 포지션 방향과 계좌 잔액을 기준으로 계산됩니다.
    2. 계산 결과를 반환합니다.

getMaxOrderSize 함수

  • 기능: 최대 주문 수량을 계산합니다.
  • 단계
    1. 주문 가능한 최대 수량은 계좌 잔액과 레버리지를 기준으로 계산됩니다.
    2. 계산 결과를 반환합니다.

getAccountAsset 함수

  • 기능: 포지션과 사용 가능한 잔액을 포함한 총 계좌 자산을 계산합니다.
  • 단계
    1. 포지션과 계좌 잔액을 기준으로 총 자본을 계산합니다.
    2. 계산 결과를 반환합니다.

calculateProfit 함수

  • 기능: 전략의 수익을 계산하고 기록합니다.
  • 단계
    1. 현재 총 수익과 초기 자산의 차이를 계산합니다.
    2. 이익을 기록하고 통계 변수를 업데이트합니다.
    3. 소득 데이터를 현지에 저장합니다.

isEnoughAssetToOrder 함수

  • 기능: 주문을 하기에 계좌 잔액이 충분한지 확인하세요.
  • 단계
    1. 계좌 잔액 정보를 받으세요.
    2. 거래 통화 유형(USDT 기반 또는 코인 기반)에 따라 필요한 자금을 계산합니다.
    3. 계좌 잔액이 주문 요구 사항을 충족하는지 확인하세요.
    4. 자금이 충분한지 여부를 나타내는 부울 값을 반환합니다.

추세 판단과 거래 논리

runInKLinePeriod 함수

  • 기능: K-라인 사이클을 기반으로 전략논리를 실행할지 여부를 결정합니다.
  • 단계
    1. 현재 K-라인이 처리되었는지 확인하세요.
    2. 처리되지 않은 경우 처리됨으로 표시하고 반환하세요.True그렇지 않으면 반환False

trendJudgment 기능(핵심 트렌드 판단 모듈)

  • 기능: 기술 지표를 기반으로 현재 추세를 파악합니다. 전체 프레임워크에서 가장 유연한 모듈입니다. 사용자는 필요에 따라 다양한 기술 지표를 대체하여 추세를 파악할 수 있습니다.
  • 현재 구현: EMA(지수 이동 평균)와 표준 편차를 결합하여 추세를 파악합니다.
  • 확장성:이 기능은 플러그형 모듈로 설계되었으며 사용자는 다음과 같은 다른 기술 지표로 쉽게 교체할 수 있습니다.
    • RSI(상대 강도 지수):매수과잉 및 매도과잉 조건 판단
    • MACD(이동평균수렴·발산): 추세 전환점 식별
    • 볼린저 밴드:가격 변동성에 따른 추세 판단
    • KDJ 지표:모멘텀과 트렌드의 결합 판단
    • 다중 지표 포트폴리오: 여러 지표를 결합하여 더욱 정확한 추세 판단이 가능합니다.
  • 단계
    1. EMA 지표를 계산하고 가격이 이를 교차하는지 확인합니다.
    2. 표준편차를 기준으로 추세인지 판단합니다.
    3. 현재 추세(롱, 숏 또는 범위)를 반환합니다.

stopLoss 함수

  • 기능:손절매 규칙에 따라 손절매 작업을 실행합니다.
  • 단계
    1. 포지션이 손절매 조건에 도달했는지 확인하세요.
    2. 해당 목표치에 도달하면 포지션이 종료되고 손절매 정보가 기록됩니다.

takeProfit 함수

  • 기능:수익 실현 규칙에 따라 수익 실현 거래를 실행합니다.
  • 단계
    1. 해당 포지션이 이익실현 조건을 충족하는지 확인하세요.
    2. 목표가격에 도달하면 해당 포지션은 종료되고 이익실현 정보가 기록됩니다.

추적 TakeProfit 함수

  • 기능: 콜백 이익실현 규칙에 따라 이익실현 작업을 실행합니다.
  • 단계
    1. 포지션이 콜백 이익 실현 트리거 조건을 충족하는지 확인하세요.
    2. 목표가격에 도달하면 해당 포지션은 종료되고 이익실현 정보가 기록됩니다.

주문 함수

  • 기능: 추세 판단 결과에 따라 주문 작업을 실행합니다.
  • 단계
    1. 현재 위치를 확인하세요.
    2. 추세 판단 결과에 따라 포지션을 개시하거나 종료합니다.

전략 핵심 논리

trendStrategy 함수

  • 기능: 전략의 핵심 논리 기능으로, 추세 판단, 손절매 및 이익 실현, 콜백 이익 실현 및 주문 작업을 실행합니다.
  • 단계
    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 플랫폼은 개발자들의 지혜와 경험이 담긴 양적 거래 지식과 전략의 보고입니다. 누구나 이곳에서 가치 있는 거래 전략과 기법을 탐색해 보시기 바랍니다. 혁신적이고 공유적인 사용자 여러분께 감사드립니다. 여러분의 기여 덕분에 이 플랫폼은 양적 거래에 대한 학습과 교류의 중요한 장이 되었으며, 모두가 기술과 전문성을 향상시키는 데 도움을 주고 있습니다.

'''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)) + "%"])
        # 持仓情况
        position_direction = ""
        forced_cover_up_price = 0
        position_profit_percent = 0
        position_profit = 0
        position_margin = 0
        if len(position) == 0:
            table_position["rows"].append(["无持仓", "-", "-", "-", "-", "-", "-", "-", "-"])
        else:
            position_direction = "多单" if position[0].Type == PD_LONG else "空单"
            [position_profit, position_profit_percent] = self.getSinglePositionProfit(position, ticker)
            position_margin = self.getSinglePositionMargin(position, ticker)
            forced_cover_up_price = self.calculateForcedPrice(account, position, ticker)
            table_position["rows"].append([exchange.GetCurrency(), self._MarginLevel, _N(position[0].Price, 4), position_direction, position[0].Amount,
                                           _N(position_margin, 4), _N(forced_cover_up_price, 4), _N(position_profit, 4), str(_N((position_profit_percent * 100), 2)) + "%"])
        # 打印表格
        LogStatus('`' + json.dumps(table_overview) + '`\n'
                  + '`' + json.dumps(table_account) + '`\n'
                  + '`' + json.dumps(table_position) + '`\n')

# main
def main():
    exchange.IO('simulate', True)

    strategy = TrendStrategy()
    
    strategy.setContract()
    
    strategy.initDatas()
    
    while True:
        
        strategy.trendStrategy()
        Sleep(strategy._Interval)