Pycon 2019 @Shenzhen

.  上一篇文章時間沒錯,2016年10月,一路向南。在金山的最後一篇博文,恰好也是向南回歸廣東的創業創意開始見起色的時間。然後就係回歸廣東,創業3年。
  準確離職時間是17年吧,都已經覺得猴年馬月了,近期時間過得好像特別快,而回看離職那時,又覺得特別漫長,或許最近有點閒吧,閒得報了個PyCon的主題,演講了一篇《從Python開始錢賺錢》。
  其實內容不是特別深奧,我也是特意簡單概括簡明扼要地講解了一下,說實話,是個有花了10個工作日精心設計了的演講,內容水中點進階,簡單得嚟又要引起疑問。最後還是說了下人生大道理,知行合一。不是說真的教大家怎麼賺錢,好吧,我的確也能盈利,更重要的是,希望可以讓大家感受下知行合一的過程,感受下自己編程的初心,感受下這一份樂趣,希望大家工作的路上不要這麼無聊地996了。
  這也權作我這麼一個從Python走出社會的人,對Python社區的一份回報吧。
  至於創業沒啥好說的,挺成功的,嗯,餓不死了,後面應該就學怎樣理財,讓我的10萬資本變100萬了:)。

PPT-PDF版本

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

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': (350,         100,         3000,    4),
        'BNBUSDT': (42,          12,          3000,    2),
        'XMRUSDT': (120,         50,          2400,    4),
}
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.2 + last6h * 0.3 + last24h * 0.2 + 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 pairs:
            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, 2)))
        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 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_inhour = timedelta < datetime.timedelta(0, 3600)
    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_inhour:
        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:
                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

        ordered = False
        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]}
            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)
        time.sleep(interval_sec)
        loop += 1


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

    with open('/etc/binanceapikey') as f:
        key = f.readline().strip()
        sec = f.readline().strip()

    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))
            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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值