. 上一篇文章時間沒錯,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