2020自动化交易程序及收益

2020自动化交易程序及收益

2020自动化交易程序及收益

一年又过去了,数字货币市场继续了巨变,并且预期继续巨变。很多不确定因素,同时也就存在了很多的机会。过去一年,在原来的程序上略加修改与完善,平稳地运行了一年,在震荡市取得了良好的收益,但是年末各币种突如其来的暴涨让该自动化交易程序有点措手不及,跑出了上界,跑输了收益。但依然相信,暴涨过后皆有暴跌,在投入极少精力的情况下取得如此收益并无惧下跌,网格的模型实属值得研发与改进。故在打破重写之后,将2020版程序放出,给有兴趣在这方面下工夫的朋友一个参考。
此为2020年4个投资币种与本人收益之于年初之变化

#!/usr/bin/python
# -*- coding: utf-8 -*-
""" One Trader
Trade some pairs once
"""
__author__ = 'Zagfai'
__date__ = '2019-02'

import random
import datetime
import math
import asyncio
import aiohttp
import json
import logging
import time
import requests
import binance.client as binanceclient
import binance.exceptions as binanceexceptions
from collections import OrderedDict

TRADE_PAIRS = {
        # PAIRX:   (upper_bound, lower_bound, capital, min_buy/sell_precise)
        'ETHUSDT': (500,         130,        7000,    4),
        'BNBUSDT': (40,          20,         7500,    2),
        'XMRUSDT': (140,         60,         7000,    4),
        'BTCUSDT': (20000,       9000,       6000,    6),
        # 'LTCUSDT': (150,         50,          1500,    4),
        # 'NEOUSDT': (14,          8.5,         1200,    3),
        # 'EOSUSDT': (6,           4,           1200,    1),
        # 'BTTUSDT': (0.0009000,   0.0007228,   1000,    0),
        # 'XRPUSDT': (0.35,        0.3,         500,     1),
}
MIN_TRADE = 0.48  # min trade percentage of volatility


class MixClient():
    proxies = {}
    # proxies = {'https': 'http://192.168.3.57:1080'}
    api_key = "" # NOQA
    api_secret = "" # NOQA

    def __init__(self, k, s):
        self.api_key = k
        self.api_secret = s
        self.c = binanceclient.Client(
            self.api_key, self.api_secret, {'proxies': self.proxies})

    def get_valuable_balance(self):
        info = self.c.get_account()
        balance = {i['asset']: round(
                    float(i['free']),
                    TRADE_PAIRS.get("%sUSDT", (0, 0, 0, 2))[3])
                   for i in info['balances']
                   if float(i['free']) > 0.0001}
        return balance

    def get_open_orders(self):
        orders = self.c.get_open_orders()
        return orders

    def get_my_last_trade(self, symbol):
        trades = self.c.get_my_trades(symbol=symbol, limit=1)
        return (round(float(trades[0]['price']), 7),
                datetime.datetime.utcfromtimestamp(trades[0]['time']/1000))

    def get_orderbook_price(self, symbol):
        # trades = self.c.get_recent_trades(symbol='LTCUSDT', limit=1)
        ticker = self.c.get_orderbook_ticker(symbol=symbol)
        return (float(ticker['askPrice']),
                float(ticker['bidPrice']))

    def get_orderbook_prices(self):
        ticker = self.c.get_orderbook_tickers()
        return (float(ticker['askPrice']),
                float(ticker['bidPrice']))

    def get_all_tickers(self):
        return {i['symbol']: float(round(float(i['price']), 7))
                for i in self.c.get_all_tickers()}

    def order(self, **params):
        return self.c.create_order(**params)

    def get_least_volatility(self, pair):
        klines = self.c.get_klines(
                symbol=pair,
                interval=binanceclient.Client.KLINE_INTERVAL_1HOUR,
                limit=168)
        blines = list(reversed([
                round((float(i[2])-float(i[3]))/float(i[3]) * 100, 2)
                for i in klines]))
        last = blines[0] * 0.8
        last6h = sorted(blines[:6])[1]
        last24h = sorted(blines[:24])[24//6]
        sf2 = sum(blines[22::24])/len(blines[22::24])
        sf1 = sum(blines[23::24])/len(blines[23::24])
        sf0 = sum(blines[::24])/len(blines[::24])
        sf = (sf2 + sf1 + sf0) / 3 * 0.8  # future two hours last week
        weighted = last * 0.4 + last6h * 0.2 + last24h * 0.1 + sf * 0.3
        if weighted > 1.5:
            weighted = 1.5
        return round(weighted, 2)


class Counter():
    def parts(self, upper, lower, delta):
        return math.log(upper / lower) / math.log(delta)

    def one_part_cap(self, cap, upper, lower, delta):
        return cap / self.parts(upper, lower, delta)

    def should_hold(self, cap, upper, lower, nowprice):
        if nowprice <= lower or nowprice >= upper:
            return None
        delta = nowprice / lower
        shares_to_hold = cap - self.one_part_cap(cap, upper, lower, delta)
        avg_bought_price = (nowprice + upper) / 2
        return shares_to_hold / avg_bought_price * nowprice


class LoggingBeautiful():
    def assets_balance(tickers, balances, pairs):
        sum_usdt = 0
        for symb in balances:
            pair = '%sUSDT' % symb
            if symb == 'USDT':
                sum_usdt += balances[symb]
            elif symb in balances and pair in tickers:
                sum_usdt += balances[symb] * tickers[pair]
        logging.info(
            "Current assets balance: %s %s" %
            (int(sum_usdt),
             json.dumps(OrderedDict(sorted(balances.items())))))

    def init_hold(tickers, pairs):
        logstrs = []
        for pi in pairs:
            pa = pairs[pi]
            s = Counter().should_hold(pa[2], pa[0], pa[1], tickers[pi])
            if not s:
                s = 0
            sh = round(s / tickers[pi], 2)
            logstrs.append("%s: %s; " % (pi, sh))
        logging.info("Should hold: " + ' '.join(logstrs))

    def market_order_info(price_info):
        logstrs = []
        for pi in price_info.values():
            price = pi.get('price')
            logstrs.append("%s (%s %s%s%% %s($%s));" % (
                pi['pair'], price,
                pi.get('trade_mark') == "BUY" and '-' or '+',
                round(pi.get('pc_delta_pct', 0), 2), pi.get('quan', 0),
                round(pi.get('quan', 0)*pi.get('price', 0), 2)))
        logging.info("Market: " + ' '.join(sorted(logstrs)))

    def minvol(pairs):
        mintradedollar = 10
        minvols = []
        logstrs = []
        for pair in sorted(pairs.keys()):
            pp = pairs[pair]
            a = mintradedollar*math.log(pp[0]/pp[1])/pp[2]
            minvol = (math.pow(math.e, a) - 1) * 100
            minvols.append(minvol)
            logstrs.append("%s: %s%%; " % (pair, round(minvol, 3)))
        logging.info("Lowest trade volatility: " + ' '.join(logstrs))
        if any(i > MIN_TRADE for i in minvols):
            logging.error("Error start with too high minimal volatility.")
            logging.error("Should lower that MIN_TRADE.")
            exit()

    def least_volatility(vols):
        logging.info("Aim volatility: %s" %
                     (json.dumps(OrderedDict(sorted(vols.items())))))


def market_info(c, pi, last_trade_price, balance, least_vola, last_trade_time):
    pair = pi['pair']
    pi['upper'], pi['lower'], pi['cap'], pi['minbs'] = TRADE_PAIRS[pair]
    pi['ask'], pi['bid'] = c.get_orderbook_price(pair)

    if last_trade_price < pi['bid']:
        pi['price'] = pi['bid']
    elif last_trade_price > pi['ask']:
        pi['price'] = pi['ask']
    else:
        pi['price'] = round((pi['ask']+pi['bid'])/2, 7)

    delta_price = abs(last_trade_price-pi['price'])
    pi['pc_delta_pct'] = delta_price / min(last_trade_price, pi['price']) * 100

    s = Counter().should_hold(pi['cap'], pi['upper'], pi['lower'], pi['price'])
    if not s:
        return False
    pi['should_hold'] = round(s, 2)
    pi['quan'] = round(pi['should_hold']/pi['price'] - balance, pi['minbs'])
    pi['trade_mark'] = pi['quan'] > 0 and 'BUY' or 'SELL'


def trade_one(c, pi, last_trade_price, balance, least_vola, last_trade_time):
    pair = pi['pair']
    pi['upper'], pi['lower'], pi['cap'], pi['minbs'] = TRADE_PAIRS[pair]
    pi['ask'], pi['bid'] = c.get_orderbook_price(pair)

    if last_trade_price < pi['bid']:
        pi['price'] = pi['bid']
    elif last_trade_price > pi['ask']:
        pi['price'] = pi['ask']
    else:
        pi['price'] = round((pi['ask']+pi['bid'])/2, 7)

    delta_price = abs(last_trade_price-pi['price'])
    pi['pc_delta_pct'] = delta_price / min(last_trade_price, pi['price']) * 100

    s = Counter().should_hold(pi['cap'], pi['upper'], pi['lower'], pi['price'])
    if not s:
        return False
    pi['should_hold'] = round(s, 2)
    pi['quan'] = round(pi['should_hold']/pi['price'] - balance, pi['minbs'])
    pi['trade_mark'] = pi['quan'] > 0 and 'BUY' or 'SELL'

    timedelta = abs(datetime.datetime.now() - last_trade_time)
    traded_inhalfanhour = timedelta < datetime.timedelta(0, 1800)
    traded_in1min = timedelta < datetime.timedelta(0, 60)
    if pi['pc_delta_pct'] < MIN_TRADE:  # No profitable
        return False
    if pi['pc_delta_pct'] < least_vola and traded_inhalfanhour:
        return False
    if pi['pc_delta_pct'] < 2.0 and traded_in1min:
        return False
    if abs(pi['quan']*pi['price']) <= 10:  # Minimax trade $10
        return False
    if pi['ask'] <= pi['lower'] or pi['bid'] <= pi['lower'] or \
       pi['ask'] >= pi['upper'] or pi['bid'] >= pi['upper']:
        return False

    order = c.order(
        symbol=pi['pair'],
        side=pi['trade_mark'],
        type='MARKET',
        quantity=abs(pi['quan']))
    logging.info("Trade:            %s %s %s as $%s (hold $%s) %s%%" %
                 (pi['pair'], pi['trade_mark'], pi['quan'],
                  round(pi['quan']*pi['price'], 2),
                  pi['should_hold'], round(pi['pc_delta_pct'], 2)))
    logging.info("OrderDetail: %s" % str(order))
    time.sleep(2)
    return True


def run(c):
    loop = 1
    interval_sec = 3
    last_trade_price_dict = {}
    last_trade_time_dict = {}
    least_vola_dict = {}
    balances = {}
    ordered = False
    while True:
        # every 1.5 hours recount least volatility
        if loop % (1.5*60*60//interval_sec) == 1:
            least_vola_dict = {
                    i: c.get_least_volatility(i)
                    for i in TRADE_PAIRS}
            LoggingBeautiful.least_volatility(least_vola_dict)

        # reset blnc & ltp or ordered
        if loop % (3*60//interval_sec) == 1 or ordered:
            balances = c.get_valuable_balance()
            for i in TRADE_PAIRS:
                time.sleep(1)
                tinfo = c.get_my_last_trade(i)
                last_trade_price_dict[i], last_trade_time_dict[i] = tinfo

        # print assets and needfix
        if loop % (30*60//interval_sec) == 1 or ordered:
            LoggingBeautiful.assets_balance(
                    c.get_all_tickers(), balances, TRADE_PAIRS)

        # check existed order
        if ordered and c.get_open_orders():
            logging.error("Order not finish.")
            time.sleep(5)
            break

        # print market frist
        if loop == 1:
            first_price_info = {}
            for pair in TRADE_PAIRS:
                if not pair.endswith('USDT'):
                    logging.error("Not support pairs base on other than USDT.")
                    exit()
                pi = {'pair': pair, 'symb': pair[:-4]}
                first_price_info[pair] = pi
                ltp = last_trade_price_dict[pair]
                ltt = last_trade_time_dict[pair]
                market_info(c, pi, ltp, balances.get(pi['symb'], 0), least_vola_dict[pair], ltt)
            LoggingBeautiful.market_order_info(first_price_info)

        ordered = False
        price_info = {}
        for pair in random.sample(TRADE_PAIRS.keys(), len(TRADE_PAIRS)):
            if not pair.endswith('USDT'):
                logging.error("Not support pairs base on other than USDT.")
                exit()
            pi = {'pair': pair, 'symb': pair[:-4]}
            price_info[pair] = pi
            ltp = last_trade_price_dict[pair]
            ltt = last_trade_time_dict[pair]
            if trade_one(c, pi, ltp, balances.get(pi['symb'], 0),
                         least_vola_dict[pair], ltt):
                ordered = True

        if loop % (10*60//interval_sec) == 1 or ordered:
            LoggingBeautiful.market_order_info(price_info)
        if ordered:
            time.sleep(25)
        time.sleep(interval_sec)
        loop += 1


if __name__ == "__main__":
    logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s',
                        level=logging.INFO)

    key = ""
    sec = ""

    logging.info("Trade started.")
    LoggingBeautiful.minvol(TRADE_PAIRS)
    LoggingBeautiful.init_hold(
            MixClient(key, sec).get_all_tickers(), TRADE_PAIRS)
    time.sleep(3)
    while True:
        try:
            run(MixClient(key, sec))
        except (requests.exceptions.ProxyError,
                requests.exceptions.ReadTimeout,
                requests.exceptions.ConnectTimeout,
                requests.exceptions.ConnectionError,
                requests.exceptions.SSLError,
                requests.exceptions.RetryError,
                requests.packages.urllib3.exceptions.ProtocolError,
                binanceexceptions.BinanceAPIException) as e:
            logging.warn("Restarted trade because of %s" % str(e))
            if 'Account has insufficient balance' in str(e):
                time.sleep(30*60)
            time.sleep(5)
        except OSError as e:
            if 'OSError("(104,' in str(e):
                logging.warn("Restarted trade because of %s" % str(e))
                time.sleep(2)
            else:
                logging.error("UNKNOWN ERROR OSError: %s" % repr(e))
                break
        except SystemExit:
            logging.error("System exit by SystemExit except.")
            break
        except Exception as e:
            logging.error("UNKNOWN ERROR: %s" % repr(e))
            break

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页