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

10개월 전

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

是的,作为一个理工男,尤其跑到澳洲这种空旷的地方来,没有任何不良嗜好,不抽烟不喝酒,也不去沾花惹草,平时除了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/

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
STEEMKR.COM IS SPONSORED BY
ADVERTISEMENT