技术图文:如何进行代码的重构?以封装 BigOne API 为例

背景

自从把“量化交易”作为自己精进的技术方向之后,我做了一些准备工作。

比如:

<u>1. 爬取交易所的公告,根据公告的信息来研判数字货币的短期走势</u>。

这里面有一个“流动性溢价”的概念,等后面我会结合一些例子跟大家聊聊这块的发现。

<u>2. 爬取平台币 One 的数据,根据数据来估计该数字货币的价值</u>。

<u>3. 封装 BigOne 交易所的 API,改进“网格交易法”做了一款自动化交易系统</u>。


技术分析

写程序的码农们所追求的一定是自己写的代码是 <u>可复用、可扩展、易维护、灵活性好</u> 的。所以,这两天我用 Layers软件体系结构风格 对自己的交易系统进行了重构。

什么是 Layers软件体系结构风格 呢?

层次系统组织成一个层次结构,每一层为上一层提供服务,并作为下一层的客户。

Layers软件体系结构风格

这种风格的优点是:

  • 支持基于可增加抽象的层的设计。这样允许将一个复杂问题分解成一个增量步骤序列的实现。
  • 支持系统改进。和管道过滤器风格一样,因为每一层最多只与上下相邻两层交互,所以改变每一层的功能最多只影响两层。
  • 支持软件复用。和抽象数据类型风格一样,只要给相邻层提供相同的接口,允许每一层用不同的方法实现。这使得在标准的接口上构建不同的实现成为可能。

基于这种风格,我把 BigOne API 封装为三层:

  • 通讯层,该层的作用是:封装 GetSet 请求,与 BigOne 服务器进行通讯,为中间层提供支撑。
  • 中间层,该层的作用是:调用通讯层,获取 BigOne 服务器返回的数据,该数据不进行处理。
  • 应用层,该层的作用是:为客户端提供对象群体,通过对象之间的协作辅助客户端实现用户需求。

代码实现

Step1 封装最底层 -- 通讯层

internal class HttpUtilManagerBigOne
{
    
    private const string ApiKey = "";//您的API Key
    private const string ApiSecret = "";//您的API Secret
    private static readonly HttpUtilManagerBigOne Instance = new HttpUtilManagerBigOne();//单例模式

    private HttpUtilManagerBigOne()
    {

    }

    public static HttpUtilManagerBigOne GetInstance()
    {
        return Instance;
    }

    public string GetToken()
    {
        DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));

        long timestamp = Convert.ToInt64(
            ((long) (DateTime.Now - startTime).TotalMilliseconds).ToString(CultureInfo.InvariantCulture)
                .PadRight(19, '0'));

        IDictionary<string, object> payload = new Dictionary<string, object>
        {
            {"type", "OpenAPI"},
            {"sub", ApiKey},
            {"nonce", timestamp}
        };
        byte[] secretKey = Encoding.Default.GetBytes(ApiSecret);
        string token = JWT.Encode(payload, secretKey, JwsAlgorithm.HS256);
        return token;
    }

    public string RequestHttpGet(string url, string param, bool withToken = false)
    {
        if (string.IsNullOrEmpty(url))
            throw new ArgumentNullException();

        if (string.IsNullOrEmpty(param)==false)
        {
            if (url.EndsWith("?"))
            {
                url = url + param;
            }
            else
            {
                url = url + "?" + param;
            }
        }

        HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
        if (request == null)
            return string.Empty;

        request.ProtocolVersion = HttpVersion.Version10;
        request.Method = "GET";
        request.Timeout = 30000;
        if (withToken)
        {
            string token = "Bearer " + GetToken();
            request.Headers.Add("authorization", token);
        }

        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
        ServicePointManager.Expect100Continue = true; 
        try
        {
            HttpWebResponse response = (HttpWebResponse) request.GetResponse();
            Stream stream = response.GetResponseStream();
            if (stream == null)
                return string.Empty;

            StreamReader reader = new StreamReader(stream, Encoding.UTF8);
            return reader.ReadToEnd();
        }
        catch
        {
            return string.Empty;
        }
    }

    public string RequestHttpPost(string url, Dictionary<string, string> arguments)
    {
        int i = 0;
        StringBuilder builder = new StringBuilder();
        foreach (KeyValuePair<string, string> item in arguments)
        {
            if (i > 0)
            {
                builder.Append("&");
            }
            builder.AppendFormat("{0}={1}", item.Key, item.Value);
            i++;
        }

        HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
        request.Method = "POST";
        request.ContentType = "application/x-www-form-urlencoded";

        string token = "Bearer " + GetToken();
        request.Headers.Add("authorization", token);

        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
        ServicePointManager.Expect100Continue = true; 

        byte[] data = Encoding.UTF8.GetBytes(builder.ToString());
        request.ContentLength = data.Length;
        using (Stream reqStream = request.GetRequestStream())
        {
            reqStream.Write(data, 0, data.Length);
            reqStream.Close();
        }
        try
        {
            HttpWebResponse resp = (HttpWebResponse) request.GetResponse();
            Stream stream = resp.GetResponseStream();

            if (stream == null)
                return string.Empty;

            StreamReader streamReader = new StreamReader(stream, Encoding.UTF8);
            return streamReader.ReadToEnd();
        }
        catch
        {
            return string.Empty;
        }
    }
}

Step2 封装中间层

internal class RestApiBigOne
{
    public string ApiEntryPoint = @"https://big.one/api/v2/";
    public string GetPing()
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "ping";
        string param = "";
        string result = httpUtil.RequestHttpGet(url, param);
        return result;
    }
    
    public string GetTickers()
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "tickers";
        string param = "";
        string result = httpUtil.RequestHttpGet(url, param);
        return result;
    }
    
    public string GetTicker(string marketId)
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "markets/" + marketId + "/ticker";
        string param = "market_id=" + marketId;
        string result = httpUtil.RequestHttpGet(url, param);
        return result;
    }

    public string GetOrderBook(string marketId)
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "markets/" + marketId + "/depth";
        string param = "market_id=" + marketId;
        string result = httpUtil.RequestHttpGet(url, param);
        return result;
    }
    
    public string GetMarketTrade(string marketId)
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "markets/" + marketId + "/trades";
        string param = "market_id=" + marketId;
        string result = httpUtil.RequestHttpGet(url, param);
        return result;
    }
    
    public string GetMarket()
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "markets";
        string param = "";
        string result = httpUtil.RequestHttpGet(url, param);
        return result;
    }
    
    public string GetAccount()
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "viewer/accounts";
        string param = "";
        string result = httpUtil.RequestHttpGet(url, param, true);
        return result;
    }
    
    public string GetOrders()
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "viewer/orders";
        string param = "";
        string result = httpUtil.RequestHttpGet(url, param, true);
        return result;
    }
    
    public string GetOrder(string orderId)
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "viewer/orders/" + orderId;
        string param = "order_id=" + orderId;
        string result = httpUtil.RequestHttpGet(url, param, true);
        return result;
    }
    
    public string CreateOrder(string marketId, string side, string price, string amount)
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "viewer/orders";
        Dictionary<string, string> param = new Dictionary<string, string>();
        param.Add("market_id", marketId);
        param.Add("side", side);
        param.Add("price", price);
        param.Add("amount", amount);
        string result = httpUtil.RequestHttpPost(url, param);
        return result;
    }
    
    public string CancelOrder(string orderId)
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "viewer/orders/" + orderId + "/cancel";
        Dictionary<string, string> param = new Dictionary<string, string>();
        param.Add("order_id", orderId);
        string result = httpUtil.RequestHttpPost(url, param);
        return result;
    }
    
    public string CancelAllOrders(string marketId)
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "viewer/orders/cancel_all";
        Dictionary<string, string> param = new Dictionary<string, string>();
        param.Add("market_id", marketId);
        string result = httpUtil.RequestHttpPost(url, param);
        return result;
    }
    
    public string GetWithdrawal()
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "viewer/withdrawals";
        string param = "";
        string result = httpUtil.RequestHttpGet(url, param, true);
        return result;
    }
    
    public string GetDeposit()
    {
        HttpUtilManagerBigOne httpUtil = HttpUtilManagerBigOne.GetInstance();
        string url = ApiEntryPoint + "viewer/deposits";
        string param = "";
        string result = httpUtil.RequestHttpGet(url, param, true);
        return result;
    }
}

Step3 封装应用层

以获取某交易对 marketId 市场挂单 买单Bid,卖单Ask 为例进行说明。

public class BigOneUtility
{
    private static readonly RestApiBigOne BigOneApi = new RestApiBigOne();
    
    public static void GetOrderBook(string marketId, out List<Ask> ask, out List<Bid> bid)
    {
        ask = default(List<Ask>);
        bid = default(List<Bid>);

        string json = BigOneApi.GetOrderBook(marketId);
        if (string.IsNullOrEmpty(json) == false)
        {
            StringReader sr = new StringReader(json);
            JsonTextReader jsonReader = new JsonTextReader(sr);
            JsonSerializer serializer = new JsonSerializer();
            GetOrderBookJson orderBook = serializer.Deserialize<GetOrderBookJson>(jsonReader);

            if (orderBook.data.asks != null)
            {
                ask = orderBook.data.asks.OrderBy(a => a.price).ToList();
            }
            if (orderBook.data.bids != null)
            {
                bid = orderBook.data.bids.OrderByDescending(a => a.price).ToList();
            }
        }
    }
}

<u>买单结构</u>

public class Bid
{
    /// <summary>
    /// bid price
    /// </summary>
    public string price { get; set; }
    /// <summary>
    /// bid amount
    /// </summary>
    public string amount { get; set; }
}

<u>卖单结构</u>

public class Ask
{
    /// <summary>
    /// ask price
    /// </summary>
    public string price { get; set; }
    /// <summary>
    /// ask amount
    /// </summary>
    public string amount { get; set; }
}

总结

到此为止,向大家介绍了自己重构 BigOne API 的整体思路,即利用分层的思想把不同功能的对象放在不同的层中,来为上一层提供服务,减少对象之间的耦合。

这是自动运行四个交易对 PRS-USDTBTM-USDTEOS-USDTONE-USDT 的截图,在这个市场里存在很多的套利机会,只要我们肯学习,再懂一些编程的技术,就会拥有大量的机会。

自动化交易

好的,今天就到这里吧! See You!

对了,到目前为止已经有9名同学 通过解码 Huffman Code 得到团队的报名方式。

团队的招新仍在进行中,对我们感兴趣的同学欢迎与我联系,我会亲自带着大家学习,一起成长!


相关图文

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容