【python数字货币量化_21精华帖_策略第9篇】永续合约、交割合约client_oid版本代码迭代1.0

[复制链接]
查看2391 | 回复0 | 2023-8-2 13:46:57 | 显示全部楼层 |阅读模式
这次迭代主要基于一个问题考虑,就是reblance的问题,短线在开单长期磨损的情况下,可能会磨损掉长线策略资金。这次将短线策略集中于永续合约,长线策略集中于交割合约。代码已提供,还有很多可精简的地方。另外该策略未使用并行模式,个人觉得,多开几个策略和并行应该是差不多的效果。
9 r& ]7 m! ^2 f/ m! l永续合约版本, J' e) f6 J/ k+ F" M
"""
+ B& m2 c$ R5 {# 程序主要思路如下:
: J0 M6 K) f1 r通过while语句,每根K线不断的循环。
8 h- p2 K9 J, j5 A/ m+ h每次循环中需要做的操作步骤
  M! s8 C, u8 {4 o' ]    1. 更新账户信息
$ z, j( x8 `8 ]) v+ l! C/ u. K  ?( A0 f    2. 获取实时数据
/ a) S3 i1 i+ \, [: e; u    3. 根据最新数据计算买卖信号8 N. C1 b/ `& k% }) M; |/ {/ p9 A2 n: c
    4. 根据目前仓位、买卖信息,结束本次循环,或者进行交易
  d1 A: c4 {8 Z1 K    5. 交易7 p$ g4 C9 r$ n
"""
4 b" s+ m' M/ C, c  Ifrom urllib.request import urlopen) V. N3 ~6 \9 q0 k5 }2 N. T# {
import ccxt8 d/ @' V2 H0 H, a
import time
- Q$ ]/ m* B) K5 t& Dimport pandas as pd# T9 ~+ j9 G; H
from datetime import datetime, timedelta# N( u7 O9 q' S5 P- W2 T6 n/ H
from time import sleep  \/ q/ ]) F1 M8 y0 p
import json1 J. d) K* k/ ?% l5 `0 W
import warnings
$ ]. `: |0 w$ v+ ^+ O/ F' q! n- Vimport requests  d- `' E& L) c" k3 k
import random% p  u! V3 ?" |
import hashlib$ {, P/ z8 e, ^& [
import hmac2 a) z' ]# p& W
import base64
) x# V& \9 `, O3 \3 g# K) v3 P6 ffrom urllib import parse
* w- ~5 J. @3 L& i6 I9 }9 Fimport numpy as np
. o* q) l2 e  P. v" h# e) i( N- zimport Signal6 s( q( b" t8 R( r& Y- P
warnings.filterwarnings("ignore")9 H, ~7 h- f0 v* O+ G4 g
pd.set_option('expand_frame_repr', False)  # 当列太多时不换行: p% \; G3 @  |8 n
pd.set_option("display.max_columns",100)7 g& C4 m1 p% c2 v5 V, c5 \5 O+ V

: v, A( x0 J  Hcoin="BTC-USDT"/ g" r( |! z0 k8 ^! Q* \! a
strategy = "vix"
- q/ t' S, O+ `
3 E  I6 z. ]2 sconfig =pd.read_csv('swap_usdt.csv',index_col="coin")
5 n' G3 H( p; M" X! C2 jexchange = ccxt.okex3()
) }0 D* M! ]; t0 B- z) Q; h  A( Yexchange.load_markets()
: D7 p9 c6 x/ ]  {, g  Nexchange.apiKey = config["apiKey"][coin]0 C. u' |5 k0 U2 u% H3 S+ ?5 T
exchange.secret = config["secret"][coin]5 F+ }! f& V7 d6 }
exchange.password=config["password"][coin]  f- M9 X7 h% P
symbol =coin + '-SWAP'
& J9 M- u, [' @: l& Mtime_interval = config["time_interval"][coin]
8 o5 a5 Z- |4 _, w9 Q" F' Ctrade_coin = symbol.split("-")[0]
4 R8 p0 Y- i5 ]4 T8 z0 Nbase_coin = symbol.split("-")[1]) E% k+ x- `# P( @
secret = config["robot_secret"][coin]; O- k  O  `" X: S$ z
robot_id = config["robot_id"][coin]+ A- v/ H! h0 X6 \7 X; e+ _
+ E6 M/ K6 C$ h9 y  F
def send_dingding_msg(content, secret=secret, robot_id=robot_id):
4 l' d; u& z# s3 a+ t5 ^! j    timestamp = int(round(time.time() * 1000))/ S! w, P( p6 X% R0 e
    secret_enc = bytes(secret.encode('utf-8'))  o5 ]" S2 A' c1 Q; f
    string_to_sign = '{}\n{}'.format(timestamp, secret)
9 Z: z  V! v, k% K# U    string_to_sign_enc = bytes(string_to_sign.encode('utf-8'))
( C9 H- c  s* [+ K; F    hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()  l* y% V" P% v. \$ Y/ A8 i
    sign = parse.quote_plus(base64.b64encode(hmac_code))1 y7 b5 P9 M# h! H5 _
    query_string = 'access_token=' + str(robot_id) + "&timestamp=" + str(timestamp) + "&sign=" + str(sign)
3 n: s; F: U0 K# ^    try:* k2 P3 N7 T5 E. w9 @7 V6 A. h
        msg = {
4 i. ?! [6 {" o, |  _, |2 P. s            "msgtype": "text",
' I) q7 B3 W: J; B- \; r/ N            "text": {"content": content + "\n" + datetime.now().strftime("%Y-%m-%d %H:%M:%S")}}6 v4 P6 Y- V; b) L
        url = 'https://oapi.dingtalk.com/robot/send'
& D+ c7 Y" p( z, p        url = url + '?' + query_string
( c4 F& m7 u4 w        body = json.dumps(msg)8 d% g3 x2 p5 A% R3 E
        # 设置重连次数2 a. ?  ?% y1 R1 g; M3 w; z$ `/ [  ~
        requests.adapters.DEFAULT_RETRIES = 15
5 w1 ^, f$ w4 B' M! X( Q* F        # 设置连接活跃状态为False! F* }9 O# ~, x
        s = requests.session()/ U( `# {# D, X' N& s) d
        s.keep_alive = False
- I) `  _( G$ J  Z        #  进行请求
1 Y. w1 V3 D7 R+ B        requests.post(url, data=body, headers={"Content-Type": "application/json ;charset=utf-8"}, timeout=5)
9 N8 w" A% I  F) H* S% H        print('成功发送钉钉')2 K9 X0 I/ z- W8 R  ~5 Z; ]
    except Exception as e:
2 m5 C3 C- l8 T5 a5 U, t5 {6 q- y        print('钉钉发送失败,尝试重复发送', e)
# F" B" L% r4 s5 s5 G0 g+ k        '''请求超时'''
8 ~- U$ g' R5 Q0 @/ ]        for i in range(1, 5):
: |4 n" m) D% S/ L* H            print('请求超时,第%s次重复请求' % i)4 ?: E2 ]/ _) i; K, k
            response = requests.post(url, data=body, headers={"Content-Type": "application/json ;charset=utf-8"},  c/ ?( y; f: H3 ~% E' F' n1 A
                                     timeout=5)5 S% |4 B& H) K- @+ ?" J
            requests.adapters.DEFAULT_RETRIES = 5
4 i9 ?9 t. Z4 T+ i. N$ m            if response.status_code == 200:
( A0 G$ L% {4 C. R                print('成功发送钉钉')
6 L( W" t' B$ S                return response
5 C7 L* g- f8 A, P2 W/ E+ j' r) S4 ^
def place_order(exchange, symbol,client_oid,order_type, buy_or_sell, price, match_price, size):( ]% E% A7 H9 H) L% e
    """/ d0 H5 ?1 t+ b$ R0 [# [
    下单
4 Y1 u  ~  @; M6 z4 Y0 U6 B    :param lever_rate:3 h5 E' x5 a# Y7 l- S
    :param exchange:
, f3 V0 ?5 _1 u8 T$ L- m+ z    :param symbol:
. A- u! N) K5 v8 F5 T* f5 R    :param order_type:' T! I, T& u3 d* M$ t; \' J3 W9 P3 a
    :param buy_or_sell: 1:开多 2:开空 3:平多 4:平空" j; `7 _' D0 Z$ h3 T6 J
    :param price:8 I0 q, j: N+ I, W' \
    :param amount: 这里的amount是合约张数
  c% f% x- U0 A, r7 V    :return:
1 s8 z+ R! ]4 D; ?& x! Q    """, k% T2 c+ ~( r( O
    if order_type == '1':
% S; @, _; l* t$ b; `        match_price = '1'5 B' q& H: W- D2 D" j) w
    elif order_type == '0':
$ G6 }1 e7 [) x; B        match_price = '0'1 n/ _) D( E( B$ K8 ^' ?' W
    if buy_or_sell == '开多':
4 c4 _( G! M3 c" d* f5 a9 k        type = '1'8 u0 }3 J2 S% [0 `
    elif buy_or_sell == '开空':) `. R& B# F; z/ v- m0 [
        type = '2'6 o6 s" C- o2 R; b3 H
    elif buy_or_sell == '平多':
. g9 t/ W7 n; B, O% @; r        type = '3'
+ b, y8 ~! ?9 [: m) `$ x% n8 s    elif buy_or_sell == '平空':9 I& T3 [2 s5 h; S6 M: e% s& f  C
        type = '4'
4 k$ k4 S' s  i; u2 ^    # 下单
# M) Q: S" e# O% Z, ~    for i in range(20):6 A4 i+ Q6 f  P- @! W" O# x8 m
        try:
1 I! t" B0 D% L5 r$ ]/ r, R            order_info = exchange.swap_post_order(params={'client_oid': client_oid,
' b! e, S% y" |) {; ]) b                                                             'type': type,
$ t% O& v& l- [7 L/ o                                                             'instrument_id': symbol,
  G5 u: H  V3 T! z% N* g* m                                                             'order_type': 0,
2 }, x  K5 x# M7 G6 N5 C                                                             'price': price,9 O, W' ]- U. ^, \& Q1 _
                                                             'size': size,: e9 [* W; a  A. T/ z8 ?
                                                             'match_price': match_price,7 r; u7 }" J9 u
                                                             'leverage': 3
: Y* X1 a! [6 R0 W                                                          }   )
+ O+ |5 l' w+ A3 X/ `            print('okex 合约交易下单成功:', symbol,  buy_or_sell, price, size)& C& a7 }5 R* a* ?/ m# O! x  w
            print('下单信息:', order_info, '\n')
- L, T: r" c; p            return order_info9 l1 K9 ^1 ?* i; n

& c( C% e, l3 _8 c3 T. F; P9 [        except Exception as e:
9 |+ Z+ v  h+ w. [            if "error_code" in str(e) and "35014" in str(e):% s; H6 D( E' H) o3 u( f
                _df = exchange.swap_get_instruments_instrument_id_price_limit(params={'instrument_id': symbol})2 K. r* r6 G0 C2 w) F
                limit_high = float(_df["highest"])7 f0 t, c+ Q( k/ ~! ^" M3 G* u
                limit_low = float(_df["lowest"])) S* T% v, r6 b1 N
                print("限价数据:", limit_high, limit_low, "下单价格:", price)
/ F5 M, ], R  j! a& q0 F( g9 F+ j# S                if buy_or_sell in ["开多", "平空"]:
! V: Y. D% R9 r( i( ^                    price = limit_high * 0.995
+ B# G5 @5 s: C. t/ J                else:
  V/ P# _% T: S) \                    price = limit_low * 1.005. I$ K& {+ |- K( [+ F* F$ A
                print("修改后的下单价格:", price)
2 q" o- ^, D2 R9 k                time.sleep(0.5), w* h6 h0 L7 V7 U) ^7 H% X
            if "error_code" in str(e) and "35002" in str(e):3 F! g0 d: m) i1 `) E2 F& z2 v
                time.sleep(15)
" {/ Q1 G+ X/ m( J. Z            if "error_code" in str(e) and "35004" in str(e):6 m" S9 h+ o3 }5 C  ?
                time.sleep(15)
" i0 Y$ b6 x: ^. R- i( f            print(e)
+ L+ v* B% {- ?    raise ValueError('okex 合约交易下单报错次数过多,程序终止')# X( w' O" V! G" F3 x
    print('下单报错次数过多,程序终止')! W- e/ B- V# F. C" M+ e$ F
    send_dingding_msg('下单报错次数过多,程序终止')
; \2 _6 O. G' e% j
. m# r2 a& @+ ?4 Y/ v4 U' `$ d4 zdef fetch_account_info(exchange,symbol,client_oid):! p/ A/ i  d4 E. M# f9 [7 @+ n. r' ^
    """
  ?" Y9 ^; [  e. e! `0 {    获取账户信息
; U# c+ x/ X) B1 w, z7 Q    :param exchange: 交易所( n; N/ z& P6 N& d1 [4 }
    :return:
' I6 d& e; M& N, E    :param account_info: dict形式,包含账户和仓位信息) E. z3 m% L! h+ `  y
    """
/ c- d: C* E- R& ]1 }    # 创建用于存放账户信息的变量
3 m# j9 v  s6 u2 H3 {: N! ]5 x1 v) a    account_info = {'账户余额': 0, '多头仓位数量': 0, '空头仓位数量': 0, '仓位成本': 0}
4 L/ F! [) b5 N    while True:
* X' S5 O: U1 D0 v, E, C       # 获取账户的资产信息" N  D0 j3 J( m2 u# `; p0 X
        try:* [% `3 ]6 F! a6 m( \" ?
            data = exchange.swap_get_instrument_id_accounts(params={'instrument_id':symbol})  # 从交易所获取账户balance信息
- U* V; X5 D! l/ s1 M% |        except Exception as e:
# h5 `3 ?5 L/ @) v1 y( f; N            send_dingding_msg('获取账户信息失败')
# a8 c8 N; _5 t* f( E/ a            print(e)( m2 m; q0 @+ K- Y! o
            sleep(0.2)8 x5 F$ T9 J1 V6 V
            continue7 a- Q/ D2 A5 x6 i2 a
         # 将数据转化为df格式
" D  [4 n/ A+ ?" T' V        account_info['账户余额'] = float(data['info']["equity"])5 Y7 L4 w; X% z2 G( B: l" o

% O4 c! }5 o7 f' ~8 F8 l8 j* h( P        # 获取账户的持仓信息- B5 @  s2 ]4 I; u% I
        try:% ]( j8 E3 m; r) a! d
            client_oid_info = exchange.swap_get_orders_instrument_id_client_oid(
0 t" z1 Y5 [3 [- L' x                params={'instrument_id': symbol, "client_oid": client_oid})2 u. z  D4 u$ l# b- a8 O* M
        except Exception as e:
' U% P( J1 e9 o- i  _            try:
1 D2 \  i& i/ U4 V                if "error_code" in str(e) and "35029" in str(e):9 D: b' p' S8 I9 c) O
                    account_info['空头仓位数量'] = 02 P( n) O5 H: M+ |# K- i3 y" U
                    account_info['多头仓位数量'] = 0
7 G/ \3 F, `# p/ N                    account_info['仓位成本'] = 0
9 ~7 x  B/ ], P3 E8 Z- Z" z                    break
5 e) K( C" A, a) U7 o                time.sleep(0.05)
# p) g9 C# k% a) f- T            except:3 i2 {% j* V- ^, |+ _5 ^1 x% F1 p
                time.sleep(0.05)
5 `; L  l( ?! T# q1 P- n            continue
, a1 X- f( j$ m' P" {
; @4 ~" s" {: m; A        if client_oid_info:  # 当持仓信息信息不为空时/ _$ o# J4 U4 Q$ Y
            if client_oid_info['state'] == "2":
8 E3 G; _# @4 O5 C, K                if client_oid_info['type'] == "1":  # 当持仓信息信息不为空时: b, r1 M/ q4 g- x& D# p1 ?
                    account_info['多头仓位数量'] = float(client_oid_info['filled_qty'])
, ~* G/ D2 U; H1 e# Y                    account_info['仓位成本'] = float(client_oid_info['price_avg'])  Q( Z3 ^# M" s
                elif client_oid_info['type'] == "2":  # 当持仓信息信息不为空时) R. |' t8 p0 v8 Z4 U& ]
                    account_info['空头仓位数量'] = float(client_oid_info['filled_qty']), |8 h+ @; H8 t4 P5 }! R( ]" b; d
                    account_info['仓位成本'] = float(client_oid_info['price_avg']). t7 J9 R! ]! `( s! x# i& J9 v
                elif client_oid_info['type'] == "3" or "4":  # 当持仓信息信息不为空时
- X: V& Z1 H' Y+ _' b                    account_info['空头仓位数量'] = 0
& O* v8 m% J' S- G- g) E  g                    account_info['多头仓位数量'] = 01 j3 s) E2 @$ G
                    account_info['仓位成本'] = 0, t$ [4 a- p6 |! J* `2 Q
        break2 q+ t9 \7 I1 }' t% v& c9 f  |
    return account_info( `0 T' S0 T% m0 A4 r6 V% Y

( j3 R, i) N& N. X9 fdef get_history_candle_data(exchange, symbol, time_interval, max_len, max_try_amount=20):
" b/ e4 |8 K9 D. N8 H+ I( C    """( T. u" Q1 Z$ ]) U+ {& \
    获取某个币种在okex交易所所有能获取的历史数据,目前v3接口最多获取1440根0 p) N  g0 W6 ?& f' q
    :param exchange:, T& A* O9 b/ T2 L
    :param symbol:+ n: d0 ~- a5 m
    :param time_interval_str:
+ f0 k* y% e+ k1 Z    :param now_milliseconds:# X! k. n  T' C* n. e0 K
    :param max_len:+ T# T$ I" x3 v' j" E2 x0 p* W
    :param max_try_amount:
4 A/ E/ m: z4 x5 A- o    :return:
% h" O9 S, T9 x' D    """
3 g0 w. o$ J# z# R    now_milliseconds = int(time.time() * 1e3)  # 获取统一的数据截止时间
5 J# W5 w# R% C( k" k" E    time_interval_int = int(time_interval[:-1]), R# V. e( Y7 H3 v; `) B
    time_segment = time_interval_int * 60 * 1000
; B8 r9 O& P" F' D) f    end = now_milliseconds - time_segment  m) u6 C/ U. M
    since = end - max_len * time_segment) e+ v7 j) T6 f1 `) b/ h; i9 G; y
    all_kline_data = []0 G& }/ u& F" e9 \% e5 H6 p  E
    while end - since >= time_segment:) E/ `9 W' @9 D: j
        kline_data = []- n  d) g* u; s; H4 W- F, M
        for i in range(max_try_amount):; E' p: h& e+ ^; z* @7 u
            try:
1 m. o( _2 f9 w  p                kline_data = exchange.fetch_ohlcv(symbol=symbol, since=since, timeframe=time_interval)
) p9 v: Y+ ^: j( [                break" C$ [( C7 B9 K/ X# f. t
            except Exception as e:
9 r" p" g7 v8 m) O3 ^  S& X                print(e)
( Q: f/ _* s' v; c; j, O* c                try:
, L) w) x( S2 Z* v1 H+ M  G0 _                    e_str = str(e)" F# x. ]( D! z( i1 n7 H) U/ u
                    if '35004' in e_str:
8 W% X. n* B) a5 U                        time.sleep(30): v4 |$ {, P9 W0 j9 U8 y7 W. s% X0 z0 N9 h
                    if '30014' in e_str:+ W4 S2 v; b: _* r6 i5 {
                        time.sleep(10)& [6 T0 W+ h( I5 Q) ?
                except:
5 j% p" O, r2 [: C5 K& B+ g: n                    time.sleep(10)
8 v; i$ n% v  a% \4 \7 Y& i4 K8 t  {
                if i == (max_try_amount - 1):$ t' Q  c' z3 @; b5 j9 x6 g7 r
                    print('获取历史数据时,fetch_ohlcv合约K线数据失败,失败次数过多,程序退出')( }* l- b# ]" g
                    send_dingding_msg('获取历史数据时,fetch_ohlcv合约K线数据失败,失败次数过多,程序退出')
6 _5 a' u5 X# J9 `) n0 Q2 L                    raise ValueError('获取历史数据时,fetch_ohlcv合约K线数据失败,失败次数过多,程序退出')
1 m# g0 N8 C2 y1 E* }8 A" |+ A        if kline_data:8 b& ~' P& o; \7 `+ C% X
            # 更新获取时间
# \9 D2 Z2 W" ?& f( r/ ^& A; T5 v            since = kline_data[-1][0]
! R# h2 l; Y+ ^" x/ t1 [            all_kline_data += kline_data
5 Y% |5 N1 C( Z! [  A5 ^% U$ k6 n: e3 j        else:
8 R. ]$ @( Z5 n& h6 _            break
; w# c% J; c0 y( ^' c$ ?( ]& I$ N& Q
) H% h4 I' j3 a7 G  ^" T    # 对数据进行整理
3 k; G/ b! }$ F5 M) b8 |    df = pd.DataFrame(all_kline_data, dtype=float)
% @, u3 o, W3 M# J  D! P    df.rename(columns={0: 'MTS', 1: 'open', 2: 'high', 3: 'low', 4: 'close', 5: 'volume'}, inplace=True)
7 c$ x) a: L% Z3 q7 a    df['candle_begin_time'] = pd.to_datetime(df['MTS'], unit='ms')
0 T! e; d5 \% r' j  T    df['candle_begin_time_GMT8'] = df['candle_begin_time'] + timedelta(hours=8)0 g8 x& a3 _6 q
    df = df[['candle_begin_time_GMT8', 'open', 'high', 'low', 'close', 'volume']]
. D6 ~, v; X6 I6 ~$ n2 u  N& @# u4 e& X5 r5 n. A
    # 删除重复的数据
2 x7 B4 I/ N: a3 q1 U; A: N  ^    df.drop_duplicates(subset=['candle_begin_time_GMT8'], keep='last', inplace=True)
/ W' c6 k# O' ?6 A! n    df.reset_index(drop=True, inplace=True)& t+ H% U" z1 W% r3 O4 x% ~
    df = df[:-1]! a& ]5 I/ u2 G

# p- ^. j# _. l  H  I0 J& E    return df2 D3 a2 n% b& j: n5 o; v0 t
( m2 k# ^/ `: |! s5 ]- _
def ccxt_fetch_candle_data(exchange, symbol, time_interval, limit, max_try_amount=5):
, J* `0 x4 @% v2 @' _  `+ L8 H    """
& e9 u) o/ a- o( H7 n# g. H    :param exchange:- ^# x# o% s/ ^  `
    :param symbol:
- f' C+ t7 q1 V7 e3 m    :param time_interval:8 T7 @, N7 C- c3 j" V1 _
    :param limit:: k9 V. i6 }( _+ c
    :param max_try_amount:
6 C" l- y. B5 ?. ?$ ]4 B3 [    :return:
( [- D! Z7 ~8 x# d: Z    获取K线数据
" e7 ?" k. j* W% z, H    """. t# X6 o3 N: Z9 z0 t+ `
    for _ in range(max_try_amount):
1 b6 Y2 W% B( r, C+ ?& q- F+ S, j        try:+ D7 b$ k/ _; V; {5 Y. Y; }, G9 e
            # 获取数据. p! D. B9 W' k' p
            data = exchange.fetch_ohlcv(symbol=symbol, timeframe=time_interval, limit=limit); Y! e& @5 [) p0 r8 v- r
            # 整理数据8 b7 x. q- g+ v4 u1 p* q1 N
            df = pd.DataFrame(data, dtype=float)
8 _3 X7 g( _3 e7 W+ U            df.rename(columns={0: 'MTS', 1: 'open', 2: 'high',6 @- n0 x$ [( |2 }3 V) H$ M0 H
                               3: 'low', 4: 'close', 5: 'volume'}, inplace=True)9 s% K) b, W6 O( w( n5 r
            df['candle_begin_time'] = pd.to_datetime(df['MTS'], unit='ms')
1 y3 |3 w! O3 @  f/ h+ P+ M$ M4 J2 Q            df['candle_begin_time_GMT8'] = df['candle_begin_time'] + timedelta(hours=8)
0 [- [( p( o. H7 ?1 A% l& `. c            df = df[['candle_begin_time_GMT8', 'open', 'high', 'low', 'close', 'volume']]7 Z0 Z+ I: b0 L3 ]! K; c: b9 A8 C
            return df- f" V8 I. S" I( _
        except Exception as e:
. B( ?) y! z3 p* A4 O1 \            print(e)
7 a% ]0 j$ r; v. a            print('获取fetch_ohlcv合约K线数据失败,稍后重试')  ^- @2 C0 T8 z' U& t7 n  z9 L2 E
            time.sleep(short_sleep_time)
8 o2 G* L- u+ {' ^) T. E+ y  Q1 c
/ U, f- {1 w- `) B% K    print('获取fetch_ohlcv合约K线数据失败,失败次数过多,程序退出')# U5 k9 e) l9 T8 u5 p7 G
    send_dingding_msg('获取fetch_ohlcv合约K线数据失败,失败次数过多,程序退出')
$ I$ d& Y. l' c5 b7 I# v    raise ValueError('获取fetch_ohlcv合约K线数据失败,失败次数过多,程序退出')
4 M1 C* i8 S& s6 J3 g; O6 j
& W5 S. ^9 H5 z6 c2 @! t# l' v5 k
" a7 E+ D" s3 |9 s& U
% u( U5 L: w' A( e8 Y7 C" ^! Qdef get_candle_data(exchange, symbol, time_interval, run_time, max_try_amount, candle_num):6 g( l# `1 G; W$ I0 o  W! L& ?
    """4 J7 ]9 S  Z2 g/ n* u4 O' t1 z4 ]
    :param exchange:
; |0 j7 Y  ]+ p    :param symbol_config:
7 N/ `% [. f. b. m4 m& ~+ y8 T& p    :param time_interval:* X  _) H- _! U6 L8 K* [8 C- J+ U
    :param run_time:5 w1 M$ V1 {! k. [7 c  r
    :param max_try_amount:. c- V# ^& w( N8 Z" [) q9 h% l
    :param symbol:3 s( b/ n1 p# q. F+ g# F5 S2 i& ?' r
    :param candle_num:' c( V9 u/ r$ I. e
    :return:
# B1 D  Q' x9 k+ c0 O    尝试获取K线数据,并检验质量
, n! {, k$ g4 Z8 a    """6 P; s6 y  y. [6 D
    # 标记开始时间; ~7 u5 o/ K+ A; F7 x/ X
    start_time = datetime.now()
0 V& r$ Q& u7 O8 Z    print('start get_candle_data', symbol, start_time)+ h0 V# u8 q0 H6 e* l5 j. y# J# w

/ P  Q, V4 Z9 M( R8 l/ k    # 尝试获取数据! E7 g  i7 E* w: c2 z
    for i in range(max_try_amount):
7 \  v0 ?, r' @: H5 i        # 获取symbol该品种最新的K线数据3 y+ x' h5 ]8 T6 p9 A& I  b5 r5 W% R
        df = ccxt_fetch_candle_data(exchange, symbol, time_interval, limit=candle_num)
; j) W- u$ @2 z' c6 S        if df.empty:
& F% ]! S  [6 t# A            continue
2 [8 ?* X7 P# @, z4 I) i; b( x  b) s        # 判断是否包含最新的数据: K1 W0 ^3 i0 T* G$ F/ K" I, s
        _ = df[df['candle_begin_time_GMT8'] == (run_time - timedelta(minutes=int(time_interval.strip('m'))))]2 ~" X5 Y/ ?3 F1 T3 T5 L* W4 F+ A. D3 g
        if _.empty:4 ?9 e/ A) d6 D/ s" z, b( _- T
            print('获取数据不包含最新的数据,重新获取')  ?) W, f' l* B  R$ I, r  d4 i7 o
            time.sleep(short_sleep_time)$ g# I# r1 V1 C- b; A
            continue
, G' c% j8 P8 a; m- `* Y! V- m4 _" z8 b
        else:3 A8 p$ q$ Q, o6 e
            # 获取到了最新数据$ U: Q" o( f, C  A: l' U) k* a

$ U" W$ Q. E1 U& i8 v9 r            df = df[df[&#39;candle_begin_time_GMT8&#39;] < pd.to_datetime(run_time)]  # 去除run_time周期的数据/ F% w0 g3 m9 C( y! }4 w
            print(&#39;end get_candle_data&#39;, symbol, datetime.now() - start_time)  C# H5 s/ b# l7 o
            return  df
  A0 K  j( o  X- a% [8 X) M7 Q2 j& n( f- k& w9 }, W
    print(&#39;获取candle_data数据次数超过max_try_amount,数据返回空值&#39;)
7 Z" U& f0 T& d# ]. A    return pd.DataFrame()4 \4 P6 O- s+ b9 T" ]; M

) `+ w  Q4 q. |# @  D+ `% W
3 z/ @6 N7 R* u) s, d* k# S( @  y' Y' M# Y) U3 S3 l
def next_run_time(time_interval, ahead_time=1):
7 v: V4 ]" j& i) u2 X    if time_interval.endswith(&#39;m&#39;):
1 g7 N  Y3 X" E* \        now_time = datetime.now()
2 W+ x" g" K4 I        time_interval = int(time_interval.strip(&#39;m&#39;))
, I) \4 l: N4 d6 R, B        target_min = (int(now_time.minute / time_interval) + 1) * time_interval
8 a0 r/ t( y" F' ]' ], V+ B        if target_min < 60:" U: `5 T# m8 b: G5 ~
            target_time = now_time.replace(minute=target_min, second=0, microsecond=0)
. A! a  }4 W6 ]        else:' m' H% k1 G6 k$ j) Q
            if now_time.hour == 23:
/ A& V2 E' k" m4 q                target_time = now_time.replace(hour=0, minute=0, second=0, microsecond=0), D; K5 r! k! ~' [+ n
                target_time += timedelta(days=1)3 u; F, z0 s5 [: K
            else:- L3 K' r# d, a# l  [
                target_time = now_time.replace(hour=now_time.hour + 1, minute=0, second=0, microsecond=0)+ e) k# x- D# m: L: _* q6 X9 A" |
        # sleep直到靠近目标时间之前
3 m( }$ U, `  ?% o        if (target_time - datetime.now()).seconds < ahead_time+1:( O1 Q  ?, _4 c( a" J4 \
            print(&#39;距离target_time不足&#39;, ahead_time, &#39;秒,下下个周期再运行&#39;)) C0 R+ h% ?& R+ ^1 J2 `7 L
            target_time += timedelta(minutes=time_interval)
; A3 u" V, v/ |7 e        print(&#39;下次运行时间&#39;, target_time)0 O0 D/ U2 k4 W7 B- v
        return target_time
6 Q% w1 @$ I% D. M2 a! [    else:
9 C) }% D* p7 W' P( u/ ]- D3 }        exit(&#39;time_interval doesn\&#39;t end with m&#39;)/ g. R4 k8 C4 m) H

% H- a  K. G+ ]+ G' X7 M# =====主函数) g" }' H8 n8 o' ^% H7 H
def main():
/ C3 R1 o, U3 q    # ===钉钉内容# v7 u: a! ?- I7 H- t5 c
    max_len = 300
7 {1 |% e6 B6 E: [  @2 u7 G    max_try_amount = 10
. i6 Z6 F& {, M: Z    candle_num =3
" o8 G# y3 I# P0 p  y5 W    config = pd.read_csv(&#39;swap_usdt.csv&#39;, index_col=&#34;coin&#34;)- B& v8 E  Q4 V7 v
    strategy_name=strategy.upper()4 y2 P- Q- V- i, k" h# {
    msg_content = &#39;SWAP_%s_%s策略报告:&#39; % (trade_coin , strategy_name)
7 C1 C$ y  a/ f! {# z    closebyhand = config[&#39;closebyhand&#39;][coin]  # 手贱模式6 u7 }# Z$ C* Y/ }3 q: Z
    leverage = config[&#34;leverage&#34;][coin]
6 z4 x7 }7 |0 s# f: j  e7 c, I- Y    fv = config[&#39;fv&#39;][coin]. o3 h# s/ Z4 E
    stop_lose_pct = int(config[&#34;stop_lose_pct&#34;][coin])
  D( w9 i/ A% @7 f& o    client_oid = trade_coin + time_interval + str(closebyhand) + &#39;usdt&#39; + strategy; i5 j( ?$ T. [+ \
    para = [int(config[strategy][coin])]
+ F2 `7 X3 ~9 l/ ]& \    account_info = fetch_account_info(exchange, symbol, client_oid=client_oid)
+ K( @7 {* n1 f4 z    msg_content += &#39;\n持有&#39; + str(base_coin) + &#39;:&#39; + str(round(account_info[&#39;账户余额&#39;], 2))
4 j0 Q/ K4 W) b) I# M+ I
$ P7 ~# [6 ~; C    if account_info[&#39;多头仓位数量&#39;] != 0:
/ w5 u2 j) J4 D8 u) d        msg_content += &#39;\n多头仓位数量:&#39; + str(account_info[&#39;多头仓位数量&#39;])
* ~0 v" F* y6 W2 n) O" v- Z6 l3 a        msg_content += &#39;\n仓位成本价:&#39; + str(account_info[&#39;仓位成本&#39;]): s+ S9 h! Q: [+ E+ @+ \
    elif account_info[&#39;空头仓位数量&#39;] != 0:$ Y) Q0 C: x  r6 p' t1 B& I
        msg_content += &#39;\n空头仓位数量:&#39; + str(account_info[&#39;空头仓位数量&#39;])
5 R! y% z. e' a& _+ m# }        msg_content += &#39;\n仓位成本价:&#39; + str(account_info[&#39;仓位成本&#39;])
4 r1 ^$ D# f( B# k+ l9 X! ?! g    else:2 w' W- ^6 Y" S3 e
        msg_content += &#39;\n无持仓信息&#39;
' h$ x( e6 C' @8 s7 a
5 U" [( o( D( m0 e1 _9 S    history_candle_data = get_history_candle_data(exchange, symbol,time_interval, max_len= max_len)
) s% b7 l% d% E
, @$ r+ b7 \% W8 N( a! F/ a9 v    run_time = next_run_time(time_interval): Q# o& _, ]- J0 \( D9 v
    sleep(max(0, (run_time - datetime.now()).seconds))7 ]$ e7 @) M9 X8 j( `
    while True:  # 在靠近目标时间时' g4 z8 G6 S$ C& p
        if datetime.now() < run_time:1 n: {1 H. f# K
            continue
3 h+ C  J" I. I0 l3 D$ R        else:
2 O& F( K+ Y) M/ F, ~$ U            break
$ Y+ M7 C$ s" S. M: e! U, x  \4 g, h! o9 f4 g& N* r9 N  |
    recent_candle_data=get_candle_data(exchange, symbol, time_interval, run_time, max_try_amount, candle_num)
+ E( G& ~! \# s4 y3 s4 V
/ y- e2 }* `: t8 K    df = history_candle_data.append(recent_candle_data, ignore_index=True)
5 T' T# p/ @" a. t. S' e+ Z
4 P+ W+ j( I: x# f) u+ c    df.drop_duplicates(subset=[&#39;candle_begin_time_GMT8&#39;], keep=&#39;last&#39;, inplace=True)& t* ~" x' u8 {7 V0 M1 E) r/ q4 }3 D
    df.sort_values(by=&#39;candle_begin_time_GMT8&#39;, inplace=True)  # 排序,理论上这步应该可以省略,加快速度
- J. E" G2 c  p2 q& G2 [  `9 c8 V9 j8 k( j    df = df.iloc[-max_len:]  # 保持最大K线数量不会超过max_len个
$ J( m: W: F3 ]$ h    df.reset_index(drop=True, inplace=True): }& P* `% k# h  q# C

$ |" Z! V" ^" I1 E* n. \    new_price = df.iloc[-1][&#39;close&#39;]  ?: G7 k# b- F0 ?
    msg_content += &#39;\n&#39; + str(trade_coin) + &#39;实时价格:&#39; + str(new_price)
- H; P2 A6 |' i
5 G. i3 q/ w# |5 ~) S% Z. q    df = getattr(Signal,strategy)(df, para=para)
  |+ u/ D$ n, {/ y. ?    signal = df.iloc[-1][&#39;signal&#39;]& n2 ~* T! S: L: E' b- |! H2 t
    msg_content += &#39;\n策略信号:&#39; + str(signal)
: V* ]0 f6 s5 ^
8 l' K3 `2 V# l: R5 b    # ===判断交易方向并且下单,除了无交易信号之外,总共有6种情况/ W  a% s: u) U; Y, w2 ~" g
    if signal == -1:5 I* E9 _( k' c$ L! G
        if account_info[&#39;多头仓位数量&#39;] > 0:4 S) y, k0 T# V) {$ y
            size = int(account_info[&#39;多头仓位数量&#39;])
4 q6 E1 D7 J7 \% H            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;平多&#39;,
4 I% D8 o1 f' D( O' Z                        price=new_price * 0.98,( G" r5 B! d6 x' e' @
                        match_price=&#34;0&#34;, size=size)
& }( M6 @$ k! ~! W/ ]3 i            # 更新账户信息、价格信息
9 Z* D4 W4 ^  H% t/ D/ j            sleep(1)
6 e$ \/ Q' c; T4 P1 g. S+ C            account_info = fetch_account_info(exchange, symbol, client_oid=client_oid)* |& e+ c6 b3 _" z
            new_price = exchange.fetch_ticker(symbol)[&#39;ask&#39;]
" l5 t: l6 n: ]4 s/ U4 O  d            size = int(account_info[&#39;账户余额&#39;] * leverage / (new_price * fv))% b1 s2 K7 G+ a8 Y4 I  Q
            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;开空&#39;,% q' L3 q) \) z, B/ k
                        price=new_price * 0.98,
. Z: ~& y+ y- i1 C" n1 L3 x                        match_price=&#34;0&#34;, size=size)
- K9 O8 Q0 }2 G: o8 p            msg_content += &#39;\n关闭多单并开空单&#39;0 C* h& P+ G% |6 T. {
            msg_content += &#39;\n开空交易数量:&#39; + str(size) + &#39;\n&#39;" M$ u, g! l( u: z/ e; q6 I& u/ U
        elif account_info[&#34;空头仓位数量&#34;] > 0:2 ?* l5 ^, w% _  S) d- Q
            msg_content += &#39;\n之前已开空单,本次不开单&#39;$ K% G# l/ g# Y3 d( ~
            pass
# \+ U; w& Q/ v6 Q) m; o; P        else:
, ?3 F3 {0 j$ n8 Z. ~/ T            size = int(account_info[&#39;账户余额&#39;] * leverage / (new_price * fv)): z8 Q0 b- y8 s/ X
            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;开空&#39;,# n3 R% a& k1 J. n: S
                        price=new_price * 0.98,% P  ]! p. h9 Z# F/ }" f$ _' B
                        match_price=&#34;0&#34;, size=size)
. T+ B9 @. n& D* O6 Q6 T' ^  C            msg_content += &#39;\n开空交易数量:&#39; + str(size) + &#39;\n&#39;) S, c# v  V' B; Q
    elif signal == 1:7 W7 `6 X( x/ X5 ~; A! u
        if account_info[&#34;空头仓位数量&#34;] > 0:
" \; x# X/ B) A2 w            size = int(account_info[&#39;空头仓位数量&#39;])1 P0 P( ]% H1 i6 y3 b% g
            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;平空&#39;,6 G/ F" ~6 h+ K. [0 T
                        price=new_price * 1.02,3 ]  X% R0 P$ [: C! S- l) Z
                        match_price=&#34;0&#34;, size=size)
! c5 o8 ?! v- [
+ E$ V7 h4 Y1 n( K            # 更新账户信息、价格信息# P, t  ^/ \+ T, n8 e9 y! E
            sleep(1)
" }" x  n0 P) y5 `, H( [3 b* f            account_info = fetch_account_info(exchange, symbol, client_oid=client_oid)' g6 V+ k5 `/ y' n
            new_price = exchange.fetch_ticker(symbol)[&#39;bid&#39;]) b$ H( t% {3 o' y
            size = int(account_info[&#39;账户余额&#39;] * leverage / (new_price * fv))
  ?! J9 N6 {7 J$ [$ f            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;开多&#39;,
# i1 o1 O6 g5 j! N3 k                        price=new_price * 1.02,
: j) ], N( Z  r& N/ o. }1 N1 g& b                        match_price=&#34;0&#34;, size=size)
9 u& i& M# x- O            msg_content += &#39;\n关闭空单并开多单&#39;' C$ e% f; @, U8 F) }' T9 p6 d- q
            msg_content += &#39;\n开多交易数量:&#39; + str(size) + &#39;\n&#39;
) @/ e8 o% i; g! _        elif account_info[&#34;多头仓位数量&#34;] > 0:! V- y" a% G" M- Q. M$ b3 G/ j
            msg_content += &#39;\n之前已开多单,本次不开单&#39;
8 O' N! r. v$ x# y6 }8 F* F            pass
; K! @2 n) X( D1 P! l+ }8 m        else:
' K5 q6 {+ K1 ^$ k* A            size = int(account_info[&#39;账户余额&#39;] * leverage / (new_price * fv))6 h1 f+ ^  c! Z( v, o# f
            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;开多&#39;,/ W1 c' s$ [4 a2 J
                        price=new_price * 1.02,
2 I5 y/ K' C4 @) n+ _' d' [                        match_price=&#34;0&#34;, size=size)
% P. n2 _9 ~: s8 ?$ P# K3 X            msg_content += &#39;\n开多交易数量:&#39; + str(size) + &#39;\n&#39;
6 y( Q4 t5 W7 H: e# S) o* M, H    elif signal == 0:
, z3 ?! s. X2 j! J, z0 a2 n$ W7 S  o        if account_info[&#34;空头仓位数量&#34;] > 0:
3 K, X  R4 ]3 {5 w2 v/ m$ r6 s9 u            size = int(account_info[&#39;空头仓位数量&#39;])" w2 \+ f/ Y- d6 w. f9 B
            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;平空&#39;,
1 A1 f- a: v* v                        price=new_price * 1.02,& L0 S& w. g6 \
                        match_price=&#34;0&#34;, size=size)
& ?& k3 f2 a  z  Q: d) e% x! k            msg_content += &#39;\n止损空单交易数量:&#39; + str(size) + &#39;\n&#39;
# i2 o3 B, b/ U" k: b" v        elif account_info[&#34;多头仓位数量&#34;] > 0:2 Y, s) _1 u+ ~8 i% |: c
            size = int(account_info[&#39;多头仓位数量&#39;])
' z. z( k  C7 [9 @7 Y3 N2 o- Y            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;平多&#39;,
) r* a0 ^$ {: q                        price=new_price * 0.98,! ~* }6 i2 P1 F( b
                        match_price=&#34;0&#34;, size=size)- T* U/ I1 n7 r' r/ |
            msg_content += &#39;\n止损多单交易数量:&#39; + str(size) + &#39;\n&#39;
1 x. p# O  C. \" T2 q4 Y8 O        else:
) A+ Y: O  x: D, {" u; c: [0 ?            msg_content += &#39;\n之前已平仓,本次不开单&#39;
1 k/ b, a& x2 P- S/ a            pass. b1 Q* T# m" j) ]3 s
    elif account_info[&#34;多头仓位数量&#34;] > 0:# P! M0 j1 H( S3 E1 b" a4 s
        if df.iloc[-1][&#39;close&#39;] < float(account_info[&#39;仓位成本&#39;]) * (1 - stop_lose_pct / 100):3 e; i6 o' s9 \1 s4 |# S
            size = int(account_info[&#39;多头仓位数量&#39;])
7 U* O) Q: }* N* Z3 e            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;平多&#39;,
7 U! i# L9 h- x. x' a/ d  ^                        price=new_price * 0.98,
3 p: N) h$ O  g' l; {1 D$ i                        match_price=&#34;0&#34;, size=size)  B: Q# [# F) q+ Q  _0 O1 e
            msg_content += &#39;\n止损多单交易数量:&#39; + str(size) + &#39;\n&#39;
7 J6 }3 N2 u3 ~" |# [% F    elif account_info[&#34;空头仓位数量&#34;] > 0:
8 x+ V8 R, _0 p- p/ H        if df.iloc[-1][&#39;close&#39;] > float(account_info[&#39;仓位成本&#39;]) * (1 + stop_lose_pct / 100):+ j7 G$ O/ t0 s
            size = int(account_info[&#39;空头仓位数量&#39;])
& K2 e% v: H7 S6 H" s            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;平空&#39;,7 l9 A4 _: v& _6 U
                        price=new_price * 1.02,& `! m% ?9 M3 W' o6 O( N
                        match_price=&#34;0&#34;, size=size)
7 W5 `. D4 h: K9 w/ l9 ~& |            msg_content += &#39;\n止损空单交易数量:&#39; + str(size) + &#39;\n&#39;5 q* E# {% r$ }, W) e
    else:
5 r' J7 }. d1 ?& |        msg_content += &#39;\n本周期无操作&#39;. T, d* c1 w3 q7 [. u: h6 J* H
        print(&#39;本周期无操作&#39;)
. T5 e. S  P$ a8 Q- X% H
4 T& h  S) H0 R1 g
" {# S% ?1 g" B1 s. u) l3 k9 Y    try:
  [+ j8 N5 e) v& b# C0 K6 u2 |* O3 |* Y        send_dingding_msg(msg_content)
  _/ ~  _4 @) u  v' r    except Exception as e:
% r! l' Z1 G% C9 d; [; H3 D& {        print(e)
; W! h% j9 t8 M, ]2 ]5 n5 |. X# #  =====运行主体
. S. z3 q$ G  l4 h* k9 Qwhile True:
& j! q* _; _, a( g6 e    try:
$ {% I$ b. u" K0 E/ H        main()/ y/ ^* {: J% W! Z* I. g. q
        time.sleep(10)7 E4 O4 ?8 w1 i2 Z# x4 h  V
    except Exception as e:
& i. p# m3 c4 @! B* o8 i        send_dingding_msg(&#39;系统出错,10s之后重新运行&#39;)1 i3 N+ b, A4 |" D$ J
        print(e)" x0 n) X1 [1 [6 `
        time.sleep(10)交割合约版本:
6 S' j6 Q  ]6 ]$ r& s; @&#34;&#34;&#34;. E6 J  F2 b8 E8 x$ }
# 程序主要思路如下:
! W$ y2 F( B4 J3 X! \通过while语句,每根K线不断的循环。
( U, J( @- |8 o0 i3 y每次循环中需要做的操作步骤$ F; S4 p# d& L* W* _# r
    1. 更新账户信息3 m& F9 ?* ]2 t- V4 ]  ?& g6 {
    2. 获取实时数据
% j+ O1 ~* v+ i! p: a    3. 根据最新数据计算买卖信号
! w& V0 i/ r- m3 i3 K* i    4. 根据目前仓位、买卖信息,结束本次循环,或者进行交易
, v. O- O: ?! r/ n  v% T    5. 交易! d# a6 E9 q# x% g. X" `% H( h' j
&#34;&#34;&#34;
2 A" `$ _9 C! N- l+ B- Ffrom urllib.request import urlopen
) K# e2 O+ n' ]9 W( Rimport ccxt
+ K% I+ f7 _* K# Vimport time& S$ p0 K$ ?  H( H- B( |% m0 Q% o
import pandas as pd) s& T% q4 g; j6 u( j' {. d, P
from datetime import datetime, timedelta$ [0 [- l! V$ X
from time import sleep( H- [7 `7 f. _% n$ E
import json, p( R2 j) ~3 Y$ F, y' j- C
import warnings
  K6 y! L4 a, {( {1 Eimport requests0 K+ J  V6 d$ M! X
import random, C3 n/ ?6 \& G8 G7 q! ~$ s, c/ ^- b
import hashlib$ R5 @0 `& H- b; H
import hmac) [/ \9 g/ r+ p9 S
import base64
3 y. r6 V; k/ }2 }. I% ofrom urllib import parse
; B; L& m! _- l0 H/ C3 F" wimport numpy as np* Y& ~) ?- N* q" d6 n
import Signal! w7 m2 A" t6 {  z
warnings.filterwarnings(&#34;ignore&#34;)( S/ [( z" s3 X: t& ]: c6 ]; D
pd.set_option(&#39;expand_frame_repr&#39;, False)  # 当列太多时不换行& r& g( X. d  A2 l# ^
pd.set_option(&#34;display.max_columns&#34;,100)
# A- I" H/ Q# c1 n3 `  }1 U& E; M2 D9 @& U9 o% ]6 H1 E
2 ~- p- @% N  C4 m# }
coin=&#34;BTC-USDT-15&#34;6 u$ ?8 K  K8 ^* Q& }
strategy_name =&#39;boll&#39;  V. x8 O, u, b! O

$ X) u7 \4 }' A! K% b" l0 u6 Uconfig =pd.read_csv(&#39;futures_usdt.csv&#39;,index_col=&#34;coin&#34;)
$ w+ z* r6 W/ Q$ G5 @4 W0 l0 ~( O9 `7 @exchange = ccxt.okex3()- C2 b/ e; @: f  a
exchange.load_markets()2 ^' j7 J; p: f  H
exchange.apiKey = config[&#34;apiKey&#34;][coin]
9 \; M( L! Y2 w) L( E& \# t" oexchange.secret = config[&#34;secret&#34;][coin]* m: J" t! }" e# F8 }1 X8 `
exchange.password=config[&#34;password&#34;][coin]! o5 p4 o: W) k; G
symbol = config[&#34;instrument_id&#34;][coin]
! r. X2 P# U  G! t& A# J0 x6 O
" g5 r5 }( A9 |secret = config[&#34;robot_secret&#34;][coin]9 N" y0 w7 C/ Q0 a( s
robot_id = config[&#34;robot_id&#34;][coin]
/ m( B% n' t3 Udef send_dingding_msg(content, secret=secret, robot_id=robot_id):
1 k" d* i% I& t/ o0 a6 p    timestamp = int(round(time.time() * 1000))' y2 O: p7 @6 C6 S
    secret_enc = bytes(secret.encode(&#39;utf-8&#39;))
+ n3 H. [' f9 x5 `    string_to_sign = &#39;{}\n{}&#39;.format(timestamp, secret)
8 P9 w1 Z. ?/ i* p    string_to_sign_enc = bytes(string_to_sign.encode(&#39;utf-8&#39;))$ H4 i! r7 E! w: n$ S" j1 K
    hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
1 Z( ?( o( H/ `6 g    sign = parse.quote_plus(base64.b64encode(hmac_code))9 y' U0 Q7 f# G+ X: i
    query_string = &#39;access_token=&#39; + str(robot_id) + &#34;&timestamp=&#34; + str(timestamp) + &#34;&sign=&#34; + str(sign)
+ l5 I+ b8 U! `5 ]/ x3 U& Z$ T    try:& K' Y  ]0 A+ v" q; }3 |2 t
        msg = {
: L8 [' I. D# f            &#34;msgtype&#34;: &#34;text&#34;,
0 e. n5 E0 `$ P9 d' j# ]6 e            &#34;text&#34;: {&#34;content&#34;: content + &#34;\n&#34; + datetime.now().strftime(&#34;%Y-%m-%d %H:%M:%S&#34;)}}, H; c6 c8 P3 q2 U$ Q
        url = &#39;https://oapi.dingtalk.com/robot/send&#39;
2 [3 P. p& S+ I- i8 w        url = url + &#39;?&#39; + query_string# ~4 C0 ]' H& w! ]# Z2 d5 z" w, I. ]
        body = json.dumps(msg)
7 Y. x" M6 X  c  e7 [3 G        # 设置重连次数: P" V8 b2 N5 L9 S9 ?
        requests.adapters.DEFAULT_RETRIES = 153 E& Q7 E% r( G% A( h7 f# w
        # 设置连接活跃状态为False
2 W2 S2 ]0 y7 j1 r        s = requests.session()5 ~  t  Q! p0 k  w: W+ b, u5 a
        s.keep_alive = False
% p1 L3 ]* E- u6 E* {        #  进行请求9 ^3 G0 q, [, k- T* W1 e- E
        requests.post(url, data=body, headers={&#34;Content-Type&#34;: &#34;application/json ;charset=utf-8&#34;}, timeout=5)
' Z8 ^" p' \# {; U- m3 m6 ~        print(&#39;成功发送钉钉&#39;)
+ X9 b( b0 F, F: |; s- R    except Exception as e:( v) A% i& i! n5 p/ ~
        print(&#39;钉钉发送失败,尝试重复发送&#39;, e)
0 b) h$ u: F6 Z+ F        &#39;&#39;&#39;请求超时&#39;&#39;&#39;
! Q: ], N  o; ?9 j8 K( [. X8 T        for i in range(1, 5):
# {8 j- a( W! ]            print(&#39;请求超时,第%s次重复请求&#39; % i)9 q7 k$ X) ^; H: j0 t- m7 P
            response = requests.post(url, data=body, headers={&#34;Content-Type&#34;: &#34;application/json ;charset=utf-8&#34;},
+ K6 R1 ~+ m  E9 h2 K' u* e3 C2 u                                     timeout=5)1 R2 ?' F. w, Y
            requests.adapters.DEFAULT_RETRIES = 58 [4 |3 R; t& G
            if response.status_code == 200:
- b# [% q% L$ h4 k2 ~3 p" u  J                print(&#39;成功发送钉钉&#39;)6 R) x. H; v9 [+ f$ Q
                return response+ }9 [+ A4 y8 s+ k: E& j, {/ g
# G, ?6 t( W$ q- v1 f
def place_order(exchange, symbol, client_oid, order_type, buy_or_sell, price, match_price, size):
$ y. S9 K4 `) }# L( K  |1 Y5 k6 L    &#34;&#34;&#34;* p$ N- Q3 W9 J) Z
    下单
5 u  Q3 _/ L0 y' B! ^' W5 b    :param lever_rate:! I' s4 c* J' A1 q& Y
    :param exchange:
  Z- _) n2 ]7 N7 ?! f    :param symbol:
4 H. B4 U  s# \$ ~) Y8 p  P    :param contract_type: 合约类型: this_week:当周 next_week:下周 quarter:季度
& x- {' c( G- y7 L4 b) D    :param order_type:
+ i- x5 W! X# n4 ?" g( i* w4 h/ p    :param buy_or_sell: 1:开多 2:开空 3:平多 4:平空
/ v; k% U0 w+ o" ?$ ?    :param price:. E$ @( `& j/ ?" O, i1 O1 v
    :param amount: 这里的amount是合约张数) Z& L* U1 H' p. l' ?0 B5 b
    :return:" q7 K( V( a. ?7 V
    &#34;&#34;&#34;
- {- B( o( s% X' c9 s    if order_type == &#39;1&#39;:( s' u2 c$ O  i
        match_price = &#39;1&#39;
# v' Q$ O& \1 P* a    elif order_type == &#39;0&#39;:
" e$ F  z! ]0 H" ?, x7 ]        match_price = &#39;0&#39;- x# `9 C  b5 W3 X# w
    if buy_or_sell == &#39;开多&#39;:
1 S7 E8 [$ E9 k3 a9 _* Y        type = &#39;1&#39;
9 }9 F/ [# h: |. Z4 G    elif buy_or_sell == &#39;开空&#39;:5 j: {" i) e& V& ~1 G9 |* T  y5 ^! ]# P
        type = &#39;2&#39;6 {+ K4 y( z2 e" p; C9 {
    elif buy_or_sell == &#39;平多&#39;:
$ C* C" Z# e: u( `: b* t2 e3 [+ n        type = &#39;3&#39;. t- s/ ~1 R# k7 Q5 d' \
    elif buy_or_sell == &#39;平空&#39;:! M/ a! U3 P+ C7 _( m# ^
        type = &#39;4&#39;+ k# X4 k  l2 O1 |( ], ]
    # 下单
( B# V7 d: K3 `0 K( i    for i in range(10):
9 Q- b0 h# Q' c' W6 C- F$ ]7 w8 z9 `  g        try:3 ?- f2 O7 U5 @& n) e: Q6 w. H
            order_info = exchange.futures_post_order(params={&#39;client_oid&#39;: client_oid,
/ w" F% X, r) G9 D, O                                                             &#39;type&#39;: type,- l' L, V+ {2 B* t* M- o6 J
                                                             &#39;instrument_id&#39;: symbol,
1 H) ~$ A" s. m8 Z  |  E                                                             &#39;order_type&#39;: 0,
& g3 Q( P0 m7 j' O$ |4 ?                                                             &#39;price&#39;: price,+ V& h' J- }0 d' y0 j  E/ ?5 W
                                                             &#39;size&#39;: size,
* a0 i! B, c1 B5 [  v* {& _  c* @                                                             &#39;match_price&#39;: match_price,
0 ~- D5 ~9 A% m$ ]$ R                                                             &#39;leverage&#39;: 3
* B! I1 B. U2 s4 U/ v                                                             }
. F) a3 Q' o! I& P! U                                                     )5 M0 E% b" A4 Y  i
            print(&#39;okex 合约交易下单成功:&#39;, symbol, buy_or_sell, price, size)5 A/ F  R- k4 s( G0 ~4 ]
            print(&#39;下单信息:&#39;, order_info, &#39;\n&#39;)
( E5 C' q' }, U7 a  C0 B            return order_info
) ]: Y5 {0 u( D. Y9 Z1 G) T* P% ?( k' I1 v  m5 C- g7 p/ {+ U4 I
        except Exception as e:
! h; f8 D# S* d2 v6 {' V            print(e)
# W$ C- ~. E' s! A% ]0 I  ^            if &#34;error_code&#34; in str(e) and &#34;32019&#34; in str(e):
$ {& c2 ^& z! @% @                _df = exchange.futures_get_instruments_instrument_id_price_limit(params={&#39;instrument_id&#39;: symbol})
1 w- i1 P' L# I; r- g4 r! U                limit_high = float(_df[&#34;highest&#34;])
- z, v- g$ d( Z6 Z' D                limit_low = float(_df[&#34;lowest&#34;])7 L3 z9 x2 O  [! f
                print(&#34;限价数据:&#34;, limit_high, limit_low, &#34;下单价格:&#34;, price)6 K1 d# P6 G6 d( |6 R* l7 Y3 K/ t
                if buy_or_sell in [&#34;开多&#34;, &#34;平空&#34;]:
9 d/ y1 s: T+ h) i; Y7 k                    price = limit_high * 0.995
" g1 ]. x6 N2 f. ~# R7 r                else:5 e* e0 r# N+ ^
                    price = limit_low * 1.005  b! i$ k  n& B2 K
                print(&#34;修改后的下单价格:&#34;, price)  W+ O6 P* ?& G8 q4 `
                time.sleep(0.5)/ z5 |" ]. J9 E9 [) @& |+ o
            if &#34;error_code&#34; in str(e) and &#34;32025&#34; in str(e):9 n: \. L% o/ @* s# T- ?* y
                time.sleep(30)
" c4 K# t  e; }            if &#34;error_code&#34; in str(e) and &#34;30014&#34; in str(e):
. M1 p7 ]( C3 {5 p                time.sleep(2)7 l" n2 Q. Q+ B; k
    raise ValueError(&#39;okex 合约交易下单报错次数过多,程序终止&#39;)
1 k0 _' Z9 a6 H    print(&#39;下单报错次数过多,程序终止&#39;)
+ p; v! [% M0 V7 F5 n$ S    send_dingding_msg(&#39;下单报错次数过多,程序终止&#39;)
- o6 [9 c( {& u# I
* {) N$ y8 t! {2 wdef next_run_time(time_interval, ahead_time=1):
, _, H4 b- A! V! o3 l9 I5 _    if time_interval.endswith(&#39;m&#39;):. l' p0 T2 s& u2 g7 N6 _# ]: f
        now_time = datetime.now()
# I, D6 M7 p$ ]* x        time_interval = int(time_interval.strip(&#39;m&#39;))
% W2 ?4 |8 r% h" [        target_min = (int(now_time.minute / time_interval) + 1) * time_interval$ M  \7 w; b! y. X8 p" h  g+ q
        if target_min < 60:# e* r. g; G( o0 ~
            target_time = now_time.replace(minute=target_min, second=0, microsecond=0)3 ^, K0 J- {9 l
        else:
/ m- T4 B# h# W3 d2 O) [3 r- Q            if now_time.hour == 23:
" B) |. r) X& h                target_time = now_time.replace(hour=0, minute=0, second=0, microsecond=0)/ ?* q& u- V* K
                target_time += timedelta(days=1)
2 a% V( C' i2 z$ Q0 R2 D& C            else:
1 ]+ O. e8 E- z# O                target_time = now_time.replace(hour=now_time.hour + 1, minute=0, second=0, microsecond=0)
) Q9 t9 m+ j. Q% ?) V: ^        # sleep直到靠近目标时间之前
/ V9 V$ r: r8 ^; p/ D  z        if (target_time - datetime.now()).seconds < ahead_time + 1:
  f' u: Z3 W7 b            print(&#39;距离target_time不足&#39;, ahead_time, &#39;秒,下下个周期再运行&#39;)
- J; C# O; _, ]& x) U1 i            target_time += timedelta(minutes=time_interval)1 `- t6 B. Y; x' |& A+ ^7 m, @
        print(&#39;下次运行时间&#39;, target_time)
2 B, k  ]. e, H! A0 ~        return target_time
! o& _+ f, U1 Y# ^    else:
5 Q6 Y' x0 A" V7 t        exit(&#39;time_interval doesn\&#39;t end with m&#39;)  w+ `! g6 f2 C
; o/ I3 L; I7 j! T' ]$ s+ C
# ===获取账户和仓位信息
* u2 m& Q. L! Pdef fetch_account_info(exchange, trade_symbol, client_oid):0 G9 Y8 z5 e$ i7 F' d0 Z
    &#34;&#34;&#34;  \: T! A, g" J' e) M
    获取账户信息' F* E9 c$ q( g! O" y
    :param exchange: 交易所
4 O) h6 }5 g8 _% p* }+ \    :param base_coin: 基准币的名称% ], U/ x' I& X% C' I! h2 T  Z* H9 b" w
    :param trade_symbol: 交易对的名称
, S. K9 i& w0 n, h    :return:! E/ I8 ]$ p$ F# d
    :param account_info: dict形式,包含账户和仓位信息+ q% B% J6 u0 t, N' H& J
    &#34;&#34;&#34;% F+ u$ r7 C2 y) i& V6 @* Z8 [) P
    # 创建用于存放账户信息的变量$ a. s' P1 P% }; P" ^5 V
    account_info = {&#39;账户余额&#39;: 0, &#39;多头仓位数量&#39;: 0, &#39;空头仓位数量&#39;: 0, &#39;仓位成本&#39;: 0}+ R% i4 M- G* X7 d& S  c* F% ^
    while True:
5 X* D/ X* h% Y  u0 f/ w2 X        # 获取账户的资产信息# j7 I  f# f0 p- y+ E
        try:
, R% `" ?+ G+ Y  i( c* N            data = exchange.futures_get_accounts_currency(params={&#39;currency&#39;: trade_symbol})  # 从交易所获取账户balance信息8 \4 `1 F4 v/ s: z6 f
        except Exception as e:' x( x5 b2 g. i: f; e3 o
            send_dingding_msg(&#39;获取账户信息失败&#39;)
1 O  i# M9 D- W3 q* M( I            print(e)' N! n' }6 \( h
            sleep(0.5)( M+ U1 o, R9 r0 _, w* Q# N! f/ K
            continue
3 z% V. D) q! s# S+ l9 P6 i        account_info[&#39;账户余额&#39;] = float(data[&#34;equity&#34;])) P0 ~% F; N" g8 P$ _
        # 获取账户的order4 E1 Y2 T( j2 {
& K4 Y+ r' q' U4 D4 y# ]
        try:
' u* T% `/ G8 l, V1 q6 |( l            client_oid_info = exchange.futures_get_orders_instrument_id_client_oid(+ B. s1 ^% S# {; K
                params={&#39;instrument_id&#39;: symbol, &#34;client_oid&#34;: client_oid})4 F6 i# f9 x+ h
9 Y. K+ e- k" P; f0 e
        except Exception as e:
" ~, H+ I7 _1 x  H& p            send_dingding_msg(&#39;获取信息失败&#39;)
! F' p2 D3 A8 Y1 ]3 A: H            print(e)- Y2 ]' F3 J; g, T
            sleep(0.5)
+ G! S  S) z- p+ x0 o$ O            continue5 g0 _# e( b: U) Q
        if client_oid_info:
8 [, X8 ~# D% N: X! _* K            if client_oid_info[&#39;state&#39;] == &#34;2&#34;:& {1 y  r6 \; P7 L
                if client_oid_info[&#39;type&#39;] == &#34;1&#34;:  # 当持仓信息信息不为空时
; r6 d3 M, _) L, e5 I/ ?                    account_info[&#39;多头仓位数量&#39;] = float(client_oid_info[&#39;filled_qty&#39;])
1 e" H* P7 N/ @8 P) V                    account_info[&#39;仓位成本&#39;] = float(client_oid_info[&#39;price_avg&#39;])* c+ P* X" {9 Z. k; Q- l. ?
                elif client_oid_info[&#39;type&#39;] == &#34;2&#34;:  # 当持仓信息信息不为空时
; n2 }* z. F, a  T# F- r  y/ G                    account_info[&#39;空头仓位数量&#39;] = float(client_oid_info[&#39;filled_qty&#39;])
0 W- I2 [0 q  @; H$ D                    account_info[&#39;仓位成本&#39;] = float(client_oid_info[&#39;price_avg&#39;])
6 ]: A2 o; L9 q; N, E0 @                elif client_oid_info[&#39;type&#39;] == &#34;3&#34; or &#34;4&#34;:  # 当持仓信息信息不为空时
6 \: i0 k6 a% v                    account_info[&#39;空头仓位数量&#39;] = 0" X0 ?0 p3 l3 k  }' @% g
                    account_info[&#39;多头仓位数量&#39;] = 0
4 ]& k" {, d# U& B2 f/ \                    account_info[&#39;仓位成本&#39;] = 0# o& P2 r9 i$ h' D1 h
        break0 M/ r, o+ ]$ r$ a" k
    return account_info
& F0 |$ ?! W1 Q1 r/ \
' @' u3 h3 [! n( d( \3 _# xdef get_history_candle_data(exchange, symbol, time_interval, max_len, max_try_amount=20):7 t% w! I4 n9 W) ^- @
    &#34;&#34;&#34;2 E0 ?: Z. \* [) Q/ P0 X8 m: K
    获取某个币种在okex交易所所有能获取的历史数据,目前v3接口最多获取1440根5 h8 r% k& l) R+ h6 \
    :param exchange:. {1 w* N* Q0 m5 D# B) X/ y. w
    :param symbol:4 a1 {, ?  n- u/ D* T5 u' `
    :param time_interval_str:
! n8 b$ x2 p" o2 q/ i: Z8 ~( f( ?1 C    :param now_milliseconds:6 T4 I7 s- G. h  H, n
    :param max_len:. X7 V8 c( i% {- j' R& m) j
    :param max_try_amount:* y+ L7 v( {/ Y0 Z7 q8 M  N3 |4 A
    :return:
* S  r4 b9 ]. l7 @+ `/ Y; F    &#34;&#34;&#34;- E9 r+ n3 j/ l* W3 O
    now_milliseconds = int(time.time() * 1e3)  # 获取统一的数据截止时间" e, E& ~3 I/ p# |/ p
    time_interval_int = int(time_interval[:-1])
4 _0 `2 ?1 Y, C. t    time_segment = time_interval_int * 60 * 1000
3 B! K! \, T3 R: O    end = now_milliseconds - time_segment7 `2 e7 s! x$ R, u
    since = end - max_len * time_segment
9 E" W0 p+ F; ^% D- m! a! E7 S3 o# R3 B    all_kline_data = []
& \" l. ?$ |$ i' b6 k    while end - since >= time_segment:% O- T1 Y* S* k) d8 S7 H- M  U% F
        kline_data = [], n$ _* [3 ^1 c6 A1 W' D; z
        for i in range(max_try_amount):2 q7 b. Y9 y: l2 S, A# m
            try:* t/ A. d8 W. V! a( O4 B
                kline_data = exchange.fetch_ohlcv(symbol=symbol, since=since, timeframe=time_interval)+ t& Y) Q% f+ B; m" p0 x
                break
1 c& E1 M/ G! f( R8 @% {            except Exception as e:, d' |/ w8 K/ ]5 `% L7 d" l  I
                print(e)0 Y4 \5 R6 e& _0 ~
                try:. k% a5 y; x/ D
                    e_str = str(e)* j* U) p9 [& J0 q, L
                    if &#39;35004&#39; in e_str:
7 C( J; N# f5 w                        time.sleep(30)
; l$ _7 b6 ~6 S7 `( m                    if &#39;30014&#39; in e_str:
& q/ T6 X' Q6 W* Q8 e- u1 B                        time.sleep(10); ^6 k' i; x3 ]. |8 \
                except:
" O' e; V; L- g! ~                    time.sleep(10)
: `9 \: d% r3 G
$ F- l' M& l" ?: x; a                if i == (max_try_amount - 1):
9 H4 a  U5 o4 f( p$ g                    print(&#39;获取历史数据时,fetch_ohlcv合约K线数据失败,失败次数过多,程序退出&#39;)
: C0 w) L) r( Q+ f8 l                    send_dingding_msg(&#39;获取历史数据时,fetch_ohlcv合约K线数据失败,失败次数过多,程序退出&#39;)
! @+ O# K+ Z; |* ?3 k                    raise ValueError(&#39;获取历史数据时,fetch_ohlcv合约K线数据失败,失败次数过多,程序退出&#39;)) x! S0 i% K$ G% T
        if kline_data:
' i9 k. Y& ]0 L            # 更新获取时间8 x0 U% _  R3 Z
            since = kline_data[-1][0]
8 c/ |% f7 P- M0 K6 y. j% ]' n% ]/ v            all_kline_data += kline_data6 ^  A- f# j+ f: ^6 t$ N% `/ \/ x
        else:
" K- o6 `& y0 Z/ B2 I            break
9 \' k8 s9 W. |0 t) J/ g, O. u* Y7 y. L' W# F
    # 对数据进行整理
( C! H) O7 p! a" B1 ~/ _    df = pd.DataFrame(all_kline_data, dtype=float)
& x) Q4 p- p+ T6 Y2 Q    df.rename(columns={0: &#39;MTS&#39;, 1: &#39;open&#39;, 2: &#39;high&#39;, 3: &#39;low&#39;, 4: &#39;close&#39;, 5: &#39;volume&#39;}, inplace=True), T3 t6 H5 g8 N. e8 I
    df[&#39;candle_begin_time&#39;] = pd.to_datetime(df[&#39;MTS&#39;], unit=&#39;ms&#39;)
  I/ l8 [  j: Q4 ^0 j    df[&#39;candle_begin_time_GMT8&#39;] = df[&#39;candle_begin_time&#39;] + timedelta(hours=8)* B, G1 m! h6 f7 t: k! X7 O
    df = df[[&#39;candle_begin_time_GMT8&#39;, &#39;open&#39;, &#39;high&#39;, &#39;low&#39;, &#39;close&#39;, &#39;volume&#39;]]
  _. w* I2 E2 E; {' U1 ~+ o" y* S/ D/ D
    # 删除重复的数据
0 U$ \7 g# x3 n" r3 p- O( \    df.drop_duplicates(subset=[&#39;candle_begin_time_GMT8&#39;], keep=&#39;last&#39;, inplace=True)
7 O% F  `4 V; K  N/ c    df.reset_index(drop=True, inplace=True)9 y+ j% n& K$ J( k. Z% G- r
    df = df[:-1]
( }" E: ~5 c: G* I7 ~
# Z" L$ t  Z( A% |2 K: j    return df$ m5 A+ k2 h0 \$ _, k
$ W0 F4 C6 t8 p6 x& j7 @3 m9 Z" l
def ccxt_fetch_candle_data(exchange, symbol, time_interval, limit, max_try_amount=5):
- C7 s( N$ M. @$ b    &#34;&#34;&#34;6 q' P9 |/ T  V
    :param exchange:
; j3 a9 u6 f4 B% M! F, T    :param symbol:
# t( d+ \7 c! u    :param time_interval:
2 B( Y, c: [9 p    :param limit:' |% W4 u5 i( O
    :param max_try_amount:
# _7 K, N; {: ]; `6 }    :return:' a* ]6 q: Q; S  C. Q* l
    获取K线数据
2 I& a8 c( F# o& @* x+ K1 q    &#34;&#34;&#34;( I; C, V$ d; X; M. ^
    for _ in range(max_try_amount):
' o8 I1 ?4 H# h        try:# j- t4 z# o; N" B0 h6 C! c
            # 获取数据
" q' _- W  {& }' c( [            data = exchange.fetch_ohlcv(symbol=symbol, timeframe=time_interval, limit=limit)  N5 D, |  F/ e7 \, l
            # 整理数据8 g, d& k$ x% ^" Y' F0 R  _( s/ z
            df = pd.DataFrame(data, dtype=float), a. x; J# W% c
            df.rename(columns={0: &#39;MTS&#39;, 1: &#39;open&#39;, 2: &#39;high&#39;,& ]  t- i1 x' B/ j
                               3: &#39;low&#39;, 4: &#39;close&#39;, 5: &#39;volume&#39;}, inplace=True), g- b& _4 S5 B
            df[&#39;candle_begin_time&#39;] = pd.to_datetime(df[&#39;MTS&#39;], unit=&#39;ms&#39;)4 }9 Y2 @" h+ y& O
            df[&#39;candle_begin_time_GMT8&#39;] = df[&#39;candle_begin_time&#39;] + timedelta(hours=8)$ k; j0 L/ X# n' ]' ~% a- @
            df = df[[&#39;candle_begin_time_GMT8&#39;, &#39;open&#39;, &#39;high&#39;, &#39;low&#39;, &#39;close&#39;, &#39;volume&#39;]]
3 U0 h, ]; z6 J) k            return df+ i- g# q8 {: u8 U5 N# E
        except Exception as e:4 L( N0 z% t. ]2 N( L1 b
            print(e)
! u. p$ ?$ m# P# [+ H            print(&#39;获取fetch_ohlcv合约K线数据失败,稍后重试&#39;)6 S0 T  ~* t) A) e+ r7 P1 H0 Y
            time.sleep(short_sleep_time)0 ?& v2 X9 `/ \, d+ Y! x' F  R: s

" k1 j6 `! i4 r( y9 V8 _    print(&#39;获取fetch_ohlcv合约K线数据失败,失败次数过多,程序退出&#39;)
: Y6 C9 e3 m; t1 i6 ]3 S( k+ ~    send_dingding_msg(&#39;获取fetch_ohlcv合约K线数据失败,失败次数过多,程序退出&#39;)
/ L1 w0 U! {+ P1 Q    raise ValueError(&#39;获取fetch_ohlcv合约K线数据失败,失败次数过多,程序退出&#39;)
7 V/ c1 S/ V$ A, ?7 ]- ?
: G9 y& p% m3 Z* D1 m
: p. {4 s) `' k0 m( n
0 G5 x3 C, d' L2 a. U+ i' Tdef get_candle_data(exchange, symbol, time_interval, run_time, max_try_amount, candle_num):
# I" M# k0 g" u0 @    &#34;&#34;&#34;
! ]; \. l+ S2 B# p    :param exchange:2 R7 J& |: {  Q7 u
    :param symbol_config:
$ U8 S! e- S- _( G8 V5 w. t8 t6 C    :param time_interval:5 c2 W1 [# Y# v' p8 M% [- S6 m! h
    :param run_time:* I( `4 T  D6 z0 e6 V
    :param max_try_amount:
7 c! a" ~" K7 V/ G  e3 ~* L( Q7 \- S    :param symbol:
: n/ w2 t$ `$ r( F1 `2 K    :param candle_num:
+ H2 i! d  S4 \9 T% p5 `    :return:
; q3 g. }% V; b9 o/ d0 _    尝试获取K线数据,并检验质量+ O6 f3 q6 ]9 |, L0 z) U
    &#34;&#34;&#34;  V! ~8 m- S" M" j5 @0 }* [
    # 标记开始时间% q, b4 X* ~/ M0 ?7 R
    start_time = datetime.now()# G2 `  u4 C/ q+ d4 k6 N
    print(&#39;start get_candle_data&#39;, symbol, start_time)2 y7 ^  y  Q" e: n( U+ ^# k5 G

8 y  b6 q5 _& e* d8 U    # 尝试获取数据
: ^/ d) W: W) t( I. D) f. x( z" a    for i in range(max_try_amount):
; m9 s9 R" A+ t# u; g" r& K4 W        # 获取symbol该品种最新的K线数据( A4 X) t7 \: ]6 N% @
        df = ccxt_fetch_candle_data(exchange, symbol, time_interval, limit=candle_num)
9 q0 ?. x$ k9 u1 ~- F1 m        if df.empty:9 Q9 C6 U: Q1 F
            continue8 O  X' j$ Q" D& P1 I5 A
        # 判断是否包含最新的数据3 m- W. \5 ^. R% ]5 l& i) v
        _ = df[df[&#39;candle_begin_time_GMT8&#39;] == (run_time - timedelta(minutes=int(time_interval.strip(&#39;m&#39;))))]
- {4 v' R7 c$ s- z1 {2 ]8 M4 p& f        if _.empty:  {( Y) p$ O- z3 _" `. B
            print(&#39;获取数据不包含最新的数据,重新获取&#39;)7 S: \6 V) X/ l/ R
            time.sleep(short_sleep_time)
+ p0 n4 {. @3 I0 p9 Z            continue/ {. S& r! I- g0 m" ?( s" j/ L
* p0 x5 _. K; o7 H; \3 N' s2 _. R& T
        else:
1 U( W. @$ a; [8 o# U* Y            # 获取到了最新数据
4 p1 k& v7 q' K3 r9 {% ?2 N8 i% U: ~: \3 j) N
            df = df[df[&#39;candle_begin_time_GMT8&#39;] < pd.to_datetime(run_time)]  # 去除run_time周期的数据. w8 a; S* y9 K0 L  D, V6 k
            print(&#39;end get_candle_data&#39;, symbol, datetime.now() - start_time)
& q+ ?: E6 r  h/ H& u% B- Z            return  df
& M; @$ ]! ?. k. T9 n5 p) `5 [7 F6 O
    print(&#39;获取candle_data数据次数超过max_try_amount,数据返回空值&#39;)
, o5 ]' ~: W7 {, b/ E9 j6 I    return pd.DataFrame(), J' H% ]: b1 X6 V% x. O) e

" v! y7 B, _1 {% M7 R  N& b' N! K' V
% Y  }; w  `3 ~$ `$ C7 x* }, E
def main():
6 W5 U/ C! h- L& v5 \8 x+ ~    max_len = 14007 o* x8 d' f- [1 M/ J' \2 X
    max_try_amount = 10/ H& c( }" g& W
    candle_num = 5
+ ~$ B/ W0 A: I/ ]2 o- Y3 D    config = pd.read_csv(&#39;futures_usdt.csv&#39;, index_col=&#34;coin&#34;)- o* }7 r* E+ ?/ X
    time_interval = config[&#34;time_interval&#34;][coin]' @  N; j5 C! b
    trade_coin = symbol.split(&#34;-&#34;)[0]
1 R9 @/ c) {0 O    trade_symbol = symbol[0:8].lower()
% {4 P  Y* e2 E    base_coin = symbol.split(&#34;-&#34;)[1]0 B+ i" r0 T$ a  z" T  u
    msg_content = &#39;Fr_%s_%s策略%s报告:&#39; % (trade_coin, strategy_name.upper(),time_interval)
6 i! ^! s/ L/ `; }( W0 \! w& r6 L: r, q! b
    closebyhand = config[&#39;closebyhand&#39;][coin]  # 手贱模式7 _6 s: |, l9 d  h9 K
    leverage = config[&#34;leverage&#34;][coin]
$ v0 l  u$ G% }: |    fv = config[&#39;fv&#39;][coin]" z; m. b+ ~, S% y8 `3 \
    stop_lose_pct = int(config[&#34;stop_lose_pct&#34;][coin])
# ?6 N: ]& g: V+ R' i( q7 F/ F* a* e0 K0 ]8 \5 K2 E1 B! A: Q
    client_oid = trade_coin + time_interval + str(closebyhand) + strategy_name. L1 x$ W; U" }+ F- b$ C" G8 C& O' I( s
    if strategy_name ==&#39;boll&#39;:
$ }& a" a3 g( b* m  t* q        para = [int(config[&#34;boll_1&#34;][coin]), float(config[&#39;boll_2&#39;][coin])]" ?* t# y; k7 x8 a& R  I2 c
    else:
0 B* H9 f2 K. N$ n* r; b' J1 H        para = [int(config[strategy_name][coin])]( u) q  H  N$ b4 R8 b! A) e
" X  w! b. m6 T' {, Y* \
    account_info = fetch_account_info(exchange, trade_symbol, client_oid=client_oid)
6 J5 n3 [  E9 ~) n$ W    msg_content += &#39;\n持有&#39; + str(base_coin) + &#39;:&#39; + str(round(account_info[&#39;账户余额&#39;], 2))0 x1 r+ n  ^6 l( D6 G

% c0 H; T4 v; o: ^2 G    if account_info[&#39;多头仓位数量&#39;] != 0:! }7 C# j/ t* Q) r, u, x! ?
        msg_content += &#39;\n多头仓位数量:&#39; + str(account_info[&#39;多头仓位数量&#39;])
/ C3 M0 M2 N) b5 C  v" x        msg_content += &#39;\n仓位成本价:&#39; + str(account_info[&#39;仓位成本&#39;])* q& R8 v, F$ c: I& D
    elif account_info[&#39;空头仓位数量&#39;] != 0:, d. Y5 ^0 N$ z9 m/ L" _
        msg_content += &#39;\n空头仓位数量:&#39; + str(account_info[&#39;空头仓位数量&#39;])# \7 ~! ?3 _- o, d4 [: \' n
        msg_content += &#39;\n仓位成本价:&#39; + str(account_info[&#39;仓位成本&#39;])/ _. z! S6 y: W3 K4 I# h
    else:$ b, u. o* z% u% W  P8 J. M  z  r
        msg_content += &#39;\n无持仓信息&#39;
6 I! }/ f/ ^$ u: q- s; q5 L" d+ q  E# v2 S7 b" {2 _! R/ q- k
    history_candle_data = get_history_candle_data(exchange, symbol, time_interval, max_len=max_len)
" {" t8 `( ^1 p3 h) X# e+ r
: }& S, ]7 {! C* ^1 h    run_time = next_run_time(time_interval)0 E" O; V) s/ }4 J' D4 u
    sleep(max(0, (run_time - datetime.now()).seconds))
, e2 n. d) R/ A3 l% L    while True:  # 在靠近目标时间时! P: e- N! ]$ F$ q
        if datetime.now() < run_time:2 H( w0 d' U/ \% u4 ]5 O
            continue% Y/ P' H) A6 x! d% s: E7 ~
        else:
, Y8 [' U- X  B/ t( P            break
7 n5 q  w* b" x8 q2 o  ^' p$ c# v; O: f
- k) E' ?5 O% D# o! X    recent_candle_data = get_candle_data(exchange, symbol, time_interval, run_time, max_try_amount, candle_num)
5 z! C( i( S+ B: F2 J) [* C: B. {) v    df = history_candle_data.append(recent_candle_data, ignore_index=True)1 M! {" z! i# k7 x+ B& p/ b
    df.drop_duplicates(subset=[&#39;candle_begin_time_GMT8&#39;], keep=&#39;last&#39;, inplace=True)
' D) }7 p  m2 G, f# a    df = df.iloc[-max_len:]  # 保持最大K线数量不会超过max_len个
1 p' `$ F! A, |2 [; V% [+ u; o    df.reset_index(drop=True, inplace=True)
8 h2 ~, {' f& `/ U    new_price = df.iloc[-1][&#39;close&#39;]
* j' A1 L% q" a% y4 l9 P! A* l+ G    msg_content += &#39;\n&#39; + str(trade_coin) + &#39;实时价格:&#39; + str(new_price)- L9 a" Z1 g  ~8 c7 e' p
  e- F1 @4 `4 M! Q/ U, w. W7 @
    df = getattr(Signal, strategy_name)(df, para=para)
8 N' h6 t" [, I! [8 o    signal = df.iloc[-1][&#39;signal&#39;]
7 \, r9 B. a* o3 U5 X
: F( _+ g. _% h. B    msg_content += &#39;\n策略信号:&#39; + str(signal), _" j/ ~# e! |6 U# D. a) q
# T, {9 w  e* p/ e9 ~% i6 {/ V
      # ===判断交易方向并且下单,除了无交易信号之外,总共有6种情况
+ N& s/ B4 L, _, r3 p8 b5 r    if signal == -1:
: n9 {5 M8 U7 ~        if account_info[&#39;多头仓位数量&#39;] > 0:2 h: k, p" i8 ?: u/ y; e6 H
            size = int(account_info[&#39;多头仓位数量&#39;])6 g9 @" t5 e( @( d% ^- K' [
            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;平多&#39;,
+ f' A& U8 b0 c% d; e2 r                        price=new_price * 0.98,7 |" }5 L4 p1 u! ?" y
                        match_price=&#34;0&#34;, size=size)
5 }. y) q) Q+ f5 }9 P: K            # 更新账户信息、价格信息
: b( E, T3 ^# j* j2 K: r            sleep(1)4 M# O  v$ J" R: o. |
            account_info = fetch_account_info(exchange, trade_symbol, client_oid=client_oid)1 Y! s: f6 Q$ F  L5 k+ @  \
            new_price = exchange.fetch_ticker(symbol)[&#39;ask&#39;]
8 @5 r$ Z: Q% Z7 H, o! D, z            size = int(account_info[&#39;账户余额&#39;] * leverage / (new_price * fv))
; q+ U6 o: E/ T3 k            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;开空&#39;,
! U- Q4 r6 M: c0 O0 c7 ]                        price=new_price * 0.98,
& T' K9 a3 Y" w$ a                        match_price=&#34;0&#34;, size=size)
6 R2 g2 d! y7 U; t' M& D: p            msg_content += &#39;\n关闭多单并开空单&#39;
+ H0 J9 N; f0 m7 A1 ^% E6 y0 P! N: @            msg_content += &#39;\n开空交易数量:&#39; + str(size) + &#39;\n&#39;
  `/ _2 k8 I* A. w; z7 Z        elif account_info[&#34;空头仓位数量&#34;] > 0:
  m: o, ^2 F4 I* g7 i            msg_content += &#39;\n之前已开空单,本次不开单&#39;- \4 P9 r! {0 v  k
            pass
1 L8 A& v+ ^8 e        else:/ u; p  R# F2 i6 W2 k
            size = int(account_info[&#39;账户余额&#39;] * leverage / (new_price * fv))
6 A  D3 V& V# ~/ L; p0 ^) ]            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;开空&#39;,% Y- J, r8 @; L$ i& a+ }( [
                        price=new_price * 0.98,
, k! j$ F: A/ j# n! Z3 A- M                        match_price=&#34;0&#34;, size=size)
; W: a  m2 o) s7 a' D" M: u$ }            msg_content += &#39;\n开空交易数量:&#39; + str(size) + &#39;\n&#39;2 A2 a6 d, B+ E/ d' t
    elif signal == 1:$ {; _. f4 q( z, u6 Z0 ]
        if account_info[&#34;空头仓位数量&#34;] > 0:
" B0 _3 \2 u( h4 `7 W- H8 U4 c            size = int(account_info[&#39;空头仓位数量&#39;])
' Z" T" w' ?3 f! H1 c            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;平空&#39;,5 J& P7 r% c) E) e" ^3 _  s
                        price=new_price * 1.02,
0 W. B0 d! G. n% ~( N/ S                        match_price=&#34;0&#34;, size=size)! {$ v; \  Y% w
            # 更新账户信息、价格信息5 k0 [  o; z- K5 e% p9 k) Q
            sleep(1)
# s- N# y3 i6 {8 C! K            account_info = fetch_account_info(exchange, trade_symbol, client_oid=client_oid)3 o+ K% x5 l$ X; W' J0 Y
            new_price = exchange.fetch_ticker(symbol)[&#39;bid&#39;]
& C- [9 s5 O4 D" e            size = int(account_info[&#39;账户余额&#39;] * leverage / (new_price * fv))1 B9 v% p; k% |5 G6 i; q+ @( q5 c
            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;开多&#39;,
( o/ N6 w8 |/ c5 n$ c                        price=new_price * 1.02,
+ f) D1 B) }9 @+ n* {                        match_price=&#34;0&#34;, size=size)- r; `! _$ b- X, _* V' X# E5 j$ l
            msg_content += &#39;\n关闭空单并开多单&#39;
0 T8 o7 q, h" [+ q( ~, o9 C            msg_content += &#39;\n开多交易数量:&#39; + str(size) + &#39;\n&#39;5 D+ Z3 x9 `& V5 ~! l1 g3 N9 S
        elif account_info[&#34;多头仓位数量&#34;] > 0:
: Y* x! S7 d2 o; R! D& Z: R+ R            msg_content += &#39;\n之前已开多单,本次不开单&#39;% W9 D& E: R3 x! X
            pass5 G8 g5 I  c$ z3 E+ {1 k
        else:5 T# s2 K4 X' U1 O) Q" U# Y
            size = int(account_info[&#39;账户余额&#39;] * leverage / (new_price * fv))* C. {  l1 z0 o; C9 k7 n4 z- Q% s
            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;开多&#39;,) o3 F0 m% E( e" T, ^1 ]3 @
                        price=new_price * 1.02,
' G& D& A% ~1 c# d                        match_price=&#34;0&#34;, size=size)
* z7 f1 S+ K  i- F! W# g            msg_content += &#39;\n开多交易数量:&#39; + str(size) + &#39;\n&#39;( E2 U9 O, m0 }: F& U% \
    elif signal == 0:- Y  ?; n/ w! D! t1 Q; s
        if account_info[&#34;空头仓位数量&#34;] > 0:) K% Z0 w! Z& {- z0 o- U4 J
            size = int(account_info[&#39;空头仓位数量&#39;])
0 J0 E6 }% k& u# ]+ q' c" J+ e            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;平空&#39;,- l3 Y4 U. e! j6 D' K
                        price=new_price * 1.02,
! A) U+ M4 X4 O                        match_price=&#34;0&#34;, size=size)
4 v! ~& |5 _" C5 s$ o            msg_content += &#39;\n止损空单交易数量:&#39; + str(size) + &#39;\n&#39;
+ E' D; ?' O, B  s! F, R) G2 s        elif account_info[&#34;多头仓位数量&#34;] > 0:% A6 \# p; u9 R3 x( I, l' {
            size = int(account_info[&#39;多头仓位数量&#39;])% ^9 K) g" \: ^+ V, q! A
            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;平多&#39;,8 z) _5 }+ {5 i1 `2 A6 G9 E
                        price=new_price * 0.98,
8 b+ E+ D& t  k' _9 \                        match_price=&#34;0&#34;, size=size)# X# K4 d& ]6 o0 {, v; G+ P
            msg_content += &#39;\n止损多单交易数量:&#39; + str(size) + &#39;\n&#39;9 d1 H1 M& B& ^' H( _
        else:
1 h5 Q9 q7 \! K2 s            msg_content += &#39;\n之前已平仓,本次不开单&#39;
4 ]- _* M' U, [: k; U7 A            pass
5 @2 G8 E/ u' o, K    elif account_info[&#34;多头仓位数量&#34;] > 0:6 M; z5 w% ^/ |! @% [7 f# I' n
        if df.iloc[-1][&#39;close&#39;] < float(account_info[&#39;仓位成本&#39;]) * (1 - stop_lose_pct / 100):# k5 ^2 ~1 \( i6 C" b
            size = int(account_info[&#39;多头仓位数量&#39;])& r# L1 I9 G- [* D' Q; {( n
            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;平多&#39;,
# ~* o6 z! o" V9 L' N                        price=new_price * 0.98,
+ E& \& K# k2 g; w                        match_price=&#34;0&#34;, size=size)2 Z2 h7 {; E+ {# `3 j
            msg_content += &#39;\n止损多单交易数量:&#39; + str(size) + &#39;\n&#39;$ E* w+ p" R- K1 V$ P7 _
    elif account_info[&#34;空头仓位数量&#34;] > 0:
2 w+ F2 ^6 a  n5 b& C, V" \0 ~        if df.iloc[-1][&#39;close&#39;] > float(account_info[&#39;仓位成本&#39;]) * (1 + stop_lose_pct / 100):, f1 X9 }9 j0 k$ ]5 W
            size = int(account_info[&#39;空头仓位数量&#39;])
' J6 X* b* h( N# _" q+ t; V            place_order(exchange=exchange, client_oid=client_oid, symbol=symbol, order_type=&#39;0&#39;, buy_or_sell=&#39;平空&#39;,
; V0 |" J) @3 o" B1 A2 T0 d                        price=new_price * 1.02,
8 R( n7 Z9 @) W+ k5 ~2 t8 c# Y/ Y                        match_price=&#34;0&#34;, size=size)% G" x( i. J* W% g1 [( A
            msg_content += &#39;\n止损空单交易数量:&#39; + str(size) + &#39;\n&#39;4 t2 [& X2 X( ^; f+ z8 L1 v# q/ A5 T
    else:% z3 R, \8 G. ~6 Q. s2 E7 X
        msg_content += &#39;\n本周期无操作&#39;
; V$ R0 Z  I. C4 d        print(&#39;本周期无操作&#39;)4 F4 D+ y" M6 I3 j
. a; |9 Y5 ~8 e. y8 I0 O; ~
    try:
# E4 ]- u1 T: h) N        send_dingding_msg(msg_content)
/ ~" |- u2 j  [; E0 F    except Exception as e:
1 F$ Y7 r( L6 t% P; ?        print(e)
5 z- V) b  U, a! X+ g# o. M6 f$ H1 C' r/ q4 L: ]. L
    sleep(10 * 1)2 [2 [# b. s% I2 I9 e0 y! u" h
# #  =====运行主体' T. x# D) v" g
while True:
. E+ o/ X, X: w# ?/ R0 q% b    try:! q7 g$ p! b% [  i: y2 E
        main()
! m( e7 H& Q: h& N! V  M        time.sleep(10)
9 c1 W1 ^* F# I9 }7 N: x    except Exception as e:
; Y4 K: `9 y8 |        send_dingding_msg(&#39;系统出错,10s之后重新运行&#39;)7 v7 n6 Z- z7 R- v+ ^7 m9 d7 _* V
        print(e, type(e)). |$ J& ~% o6 k( M
        time.sleep(10)csv格式:
' u) \% Q$ J% s9 ]# t0 w: m交割合约:1 c, w# R6 X" b6 S
/ e, V( ^9 U2 X, D3 J  I
% i9 q+ k# ~+ R, r5 S  f4 r$ {
永续合约:
4 e2 x4 ^' h, l: P: K  ^' P# K5 }" R7 H

, C& g/ q0 C- H, G* l上架时就把) L: T1 `( Z% F/ A: @

- E0 _0 W' x* z! Fcoin=&#34;BTC-USDT-15&#34;
0 K. S. {/ i1 ^3 Wstrategy_name =&#39;boll&#39;3 x0 p5 d- U2 h! }; p. I6 Q
9 E+ J& \) ^2 D. K
这个部分修改就行9 Q% p# n" n  W- \
然后在Linux服务器上用nohup命令:
& N$ C/ R" x; {  X$ Inohup python -u btc_vix.py >1btc_vix.log 2>&1 && F. g8 {" v' C) C& U, q) `& E
搞定。这个完全是我实盘框架。比较粗糙,里面的函数可封装、模块化。框架可优化、简洁的地方比较多。  [9 n$ i6 q# @* H" {- b/ b" V5 {
另外就差一个Signal文件啦,这个就由各位自行搞定啦。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

293

金钱

0

收听

0

听众
性别

新手上路

金钱
293 元