用代码把思想变成产品的乐趣

in #cn5 years ago

曾几何时,我看国内电视节目上有个公开竞聘互联网公司产品经理的节目,里面有一个理工女孩,被问到个人爱好的时候,她说自己平时爱好啊就是折腾代码,看着代码运行出来的各种结果,心中感到无比的甜蜜。当时我太太看了以后哈哈大笑,但是作为一个理工男,我内心深感赞同,这种甜蜜的感觉类似初恋,或者像是看到从自己肚子里生个活蹦乱跳的宝宝出来。

是的,作为一个理工男,尤其跑到澳洲这种空旷的地方来,没有任何不良嗜好,不抽烟不喝酒,也不去沾花惹草,平时除了DIY前后花园,给屋子抹抹油漆,打法空闲时间的方法就是折腾代码玩,心中有了个想法,就喜欢把它实现了。 折腾的过程犹如十月怀胎,的确有有些痛苦的,但是咱痛并快乐中啊,一旦折腾出来个产品,就如同从自己肚子里生出一个婴儿一样,这种感觉不是在雇佣/被雇佣关系中做产品能够给予的。

在加密货币市场里,有一群人,甚至是机构,他们通过“搬砖”来赚钱,他们主要利用了同一种加密货币,加密货币交易所之间价格的不同,从一个交易所买低,到另外一个交易所卖高,比如,比特币在一个交易所7000刀挂牌卖,在另外一个交易所8000刀挂牌买,当然,量要够大,这么夸张的情形我还真碰到过一次,某交易所要关门前,出现匪夷所思的低价出售。甚至很有很多人或机构搞了很多“搬砖机器人”在做这个事情,这也导致交易所之间差价也越来越小了。

我做的事情就是从各个交易所通过它们提供的API,把它们支持哪些加密货币和币的当前价格取出来,然后集成为这个样子:

image.png

以币安为例,每个交易所用一个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/

Coin Marketplace

STEEM 0.22
TRX 0.20
JST 0.034
BTC 92511.70
ETH 3099.35
USDT 1.00
SBD 3.12