使用代码把idea变成product的乐趣
曾几何时,我看国内电视节目上有个公开竞聘互联网公司产品经理的节目,里面有一个理工女孩,被问到个人爱好的时候,她说自己平时爱好啊就是折腾代码,看着代码运行出来的各种结果,心中感到无比的甜蜜。当时我太太看了以后哈哈大笑,但是作为一个理工男,我内心深感赞同,这种甜蜜的感觉类似初恋,或者像是看到从自己肚子里生个活蹦乱跳的宝宝出来。
是的,作为一个理工男,尤其跑到澳洲这种空旷的地方来,没有任何不良嗜好,不抽烟不喝酒,也不去沾花惹草,平时除了DIY前后花园,给屋子抹抹油漆,打法空闲时间的方法就是折腾代码玩,心中有了个想法,就喜欢把它实现了。 折腾的过程犹如十月怀胎,的确有有些痛苦的,但是咱痛并快乐中啊,一旦折腾出来个产品,就如同从自己肚子里生出一个婴儿一样,这种感觉不是在雇佣/被雇佣关系中做产品能够给予的。
在加密货币市场里,有一群人,甚至是机构,他们通过“搬砖”来赚钱,他们主要利用了同一种加密货币,加密货币交易所之间价格的不同,从一个交易所买低,到另外一个交易所卖高,比如,比特币在一个交易所7000刀挂牌卖,在另外一个交易所8000刀挂牌买,当然,量要够大,这么夸张的情形我还真碰到过一次,某交易所要关门前,出现匪夷所思的低价出售。甚至很有很多人或机构搞了很多“搬砖机器人”在做这个事情,这也导致交易所之间差价也越来越小了。
我做的事情就是从各个交易所通过它们提供的API,把它们支持哪些加密货币和币的当前价格取出来,然后集成为这个样子:
以币安为例,每个交易所用一个python文件表示,只要实现两个函数:
def getSymbol(code): symbol_list = util.requestTimeout(url='https://api.binance.com/api/v1/exchangeInfo',timeout=waittime,errMsg='cannot get exchangeInfo in binance!') if symbol_list == None: return None for i in symbol_list.json()['symbols']: symbol = i['symbol'] quoteasset= i['quoteAsset'] if symbol.startswith(code.upper()) & (quoteasset == 'USDT'): return symbol for i in symbol_list.json()['symbols']: symbol = i['symbol'] quoteasset= i['quoteAsset'] if symbol.startswith(code.upper()) & (quoteasset == 'BTC'): return symbol return None def getUSDPrice(symbol): symbol = getSymbol(symbol) if symbol ==None: return None url = "https://api.binance.com/api/v3/ticker/bookTicker?symbol=%s"%symbol print(url) response = util.requestTimeout(url=url,timeout=waittime,errMsg='cannot get bookTicker in binance') if response is None: return None
把这些交易所文件全部包含在exchanges文件夹里,形成一个模块,最后在主文件里,每次启动前,把所有交易所的模块加载,这样以后要添加新的交易所,只要新增加一个文件放exchanges文件夹里就OK了:
import exchanges import pkgutil exchange_list = [] prefix = exchanges.__name__ + "." for importer, modname, ispkg in pkgutil.iter_modules(exchanges.__path__,prefix): print ("Found submodule %s (is a package: %s)"% (modname, ispkg)) module = __import__(modname, fromlist="exchanges") exchange_list.append(module)
在调用交易所的实时数据方面,我采用了多线程池的异步方式,这样不会因为某个调用出问题而全体卡着,这些都是后来经过不断测试改进后的方案:
def getRealtimeData(symbol): results = [] pool = ThreadPool() for exchange in exchange_list: print(exchange) results.append(pool.apply_async(exchange.getUSDPrice, args = (symbol,))) price_list = [r.get() for r in results if r is not None and r.get() is not None] pool.close() pool.join() if not price_list: return None df = pd.DataFrame(price_list) print(df) df = df[['source','bid','ask','trade']] df.columns=['Exchange','BestBid','BestAsk','Last Trade Price'] return df
返回的数据我都是用的python pandas的dataframe格式,我特别喜欢pandas这个大数据处理块,非常方便,变化多端。
整个web框架采用了react.js,有人说那玩意不是javacript才有的吗?不错,但是python把它包装成一种叫做dash的东西,配合python自带的flask,以及plot.js,非常适合数据的可视化。但是使用过程中我发现这玩意还是刚开始,有些不成熟,也有些小bug,不过对我想做的玩意来说,足够了。
这个界面设计类似写html代码:
app = dash.Dash(__name__, external_stylesheets=external_stylesheets) app.title = 'Cryptocurrency Exchanges Depth' app.layout = html.Div( [ ``` html.Div( className="app-header", children=[ html.Div('Cryptocurrency Exchanges Depth', className="app-header--title") ] ), html.Div( [ html.Label('Symbol'), dcc.Dropdown( id='symbol', options=[ {'label': symbol+' '+name, 'value': symbol} for symbol,name in symbols.items() ],value='BTC') ],style={'width': '20%', 'display': 'inline-block'}), html.Div( [ html.Div(id='my-table',style={'width': '40%', 'display': 'inline-block'}), html.Div( [ dcc.Tabs(id="tabs-history", value='tab-history', children=[ dcc.Tab(label='24 hours trend', value='tab-24-hours',style=tab_style, selected_style=tab_selected_style), dcc.Tab(label='1 month trend', value='tab-1-month',style=tab_style, selected_style=tab_selected_style), dcc.Tab(label='1 year trend', value='tab-1-year',style=tab_style, selected_style=tab_selected_style), ],style=tabs_styles), dcc.Graph(id='trend-chart') ],style={'width': '40%', 'display': 'inline-block','float':'right'}) ]), dcc.Interval( id='interval-component', interval=10*1000, # in milliseconds n_intervals=0 ), html.Div ( className="footer", children=[ html.Div([ html.P('engineerman.club'), html.P('2018 copyright'), ], style={'width': '49%', 'display': 'table-cell','vertical-align': 'middle'}) ``` ] )
然后采用一种回调函数的方式来处理用户跟界面的互动,这里链接这界面和数据处理模块:
@app.callback(Output('my-table', 'children'), [Input('symbol', 'value'),Input('interval-component', 'n_intervals')]) def table_update(selected_dropdown_value,n): df1 = getRealtimeData(selected_dropdown_value) return generate_table(df1)
最后我把这个婴儿放到亚马逊的aws上运行,还给它一个子域名,看着从自己肚子里生出来的娃,内心无比欣慰:
http://cryptodepth.engineerman.club/