avatar of 发明者量化-小小梦 发明者量化-小小梦
집중하다 사신
4
집중하다
1271
수행원

발명자의 양적 거래 플랫폼 범용 프로토콜은 맞춤형 거래소에 연결됩니다.

만든 날짜: 2017-08-24 16:29:56, 업데이트 날짜: 2021-06-10 15:33:02
comments   23
hits   17429

제너럴 프로토콜 사용 문서

이 GPL를 사용하여 모든 서비스에 액세스할 수 있습니다.API거래 거래소, 특정 API 프로토콜, 또는restwebsocketfix…에 접속할 수 있습니다. 파이썬 일반 프로토콜 예시: https://www.fmz.com/strategy/101399

  • ### 1. GPL 플러그인 실행, 포트 설정

잘 작성된 GNU/GPL 플러그인 의 감시 주소와 포트 설정. 예:http://127.0.0.1:6666/DigitalAsset또는:http://127.0.0.1:6666/exchange

왜 이런 것들을 설치해야 할까요?IP경로모직물? 왜냐하면발명가 양적페이지제어 센터점프거래소 추가페이지, 선택 GPL , 표시 제외API-KEY[서비스 주소]는 호스트가 어떤 IP와 포트를 사용해야 하는지를 알려줍니다. [서비스 주소]는 호스트와 GPL 플러그인 프로그램이 동일한 장치에서 실행되지 않을 수 있습니다.]http://127.0.0.1:6666/DigitalAssetDigitalAsset이 글은 본인이 정의한 이름의 예입니다.

발명가의 양적 추가 거래소 페이지에는 다음과 같은 내용이 있습니다. 일반적으로, 거래소에서는 계정 정보를 설정할 때만 설정합니다.access key그리고secret key그러나 일부 거래소의 API 인터페이스는 거래 암호를 전송하도록 요구합니다 (예를 들어, 일부 거래소의 주문 인터페이스는), 이러한 정보를 작성하기 위해 GPRS 페이지에 불필요한 컨트롤이 없기 때문에 이러한 거래소의 API를 만났을 때, 우리는 전달해야 할 불필요한 구성 정보를 작성 할 수 있습니다.secret key이 정보에 민감하지 않은 경우access key), 그리고 GPL 플러그인에서 문자열을 만드는split이 데이터들을 분리해서, 그림의 예에서 보여지는 것처럼

발명자의 양적 거래 플랫폼 범용 프로토콜은 맞춤형 거래소에 연결됩니다.

그리고 플러그인에서 처리하면XXX_PassWord。 예를 들어, 이 글의 마지막 전체 예제에서 newBitgo 함수는:

  func newBitgo(accessKey, secretKey string) *iBitgo {
      s := new(iBitgo)
      s.accessKey = accessKey
      s.secretKey = secretKey
      // 在此可以分离secretKey中的额外配置信息,可以写成如下注释中的内容
      /*
      arr := strings.SplitN(secretKey, ",", 2)
      if len(arr) != 2 {
          panic("配置错误!")
      }
      s.secretKey = arr[0]   // secret key 
      s.passWord = arr[1]    // XXX_PassWord
      */
      s.apiBase = "https://www.bitgo.cn"
      s.timeout = 20 * time.Second
      s.timeLocation = time.FixedZone("Asia/Shanghai", 8*60*60)

      return s
  }

발명자의 양적 거래 플랫폼 범용 프로토콜은 맞춤형 거래소에 연결됩니다.

전체적인 GPL 플러그인main함수 예시: Go이 글의 번역은:

  func main(){
      var addr = flag.String("b", "127.0.0.1:6666", "bing addr")   // 设置命令行参数,默认值描述,端口设置6666
      flag.Parse()                                                 // 解析命令行
      if *addr == "" {
          flag.Usage()                                             // 显示命令行描述
          return 
      }
      basePath := "/DigitalAsset"
      log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...")   // 打印监听端口信息
      http.HandleFunc(basePath, OnPost)
      http.ListenAndServe(*addr, nil)
  }
  • #### 2 반응 함수

GPL 플러그인 프로그램은 지정된 포트를 계속 감시하고, 요청에 의해 전송request요청이 있을 때 응답 함수 응답 실행을 호출하고, 요청 데이터의 파라미터를 분석합니다. 호스트가 보낸 요청 데이터는 다음과 같습니다.

  /* request的JSON结构,发明者量化调用GetTicker,托管者发送给通用协议插件情况举例(各个API调用时,params的值可能不一样,此处method为ticker):
  {
      "access_key" : "XXX",               // `json:"access_key"`
      "secret_key" : "XXX",               // `json:"secret_key"`
      "nonce" : "1502345965295519602",    // `json:"nonce"`
      "method" : "ticker",                // `json:"method"`
      "params" : {                        // `json:"params"`
                     "symbol" : "btc",
                     ...
                 },                       // 各个请求参数略有区别。即在策略中调用不同的 发明者量化 API会有不同的参数, 在下文各个API 有介绍说明。
  }
  */

따라서, JSON 배열을 거꾸로 JSON 배열을 거꾸로 JSON 배열을 거꾸로 JSON 배열을 거꾸로 JSON 배열을 거꾸로 JSON 배열을 거꾸로request~ 안에Method우리는 이것을 사용할 수 있습니다switch다른 발명가들의 양적 API를 분류하기 위해, 즉, 어떤 발명가가 양적이라고 호칭되는 호스트에서 실행되는 정책을 식별하기 위해API) 호출:

Go언어 예시:

  switch request.Method {    // 此处request.Method的M为大写,通用协议程序接收到的请求主体中为JSON数据,在Go语言中反JSON序列化(Unmarshal)为结构体,字段首字母必须大写
    case "accounts" :        // 当托管者上的机器人策略中调用了exchange.GetAccount()函数,托管者会发送来请求,其中Body携带的数据中method属性值为accounts
        data, err = e.GetAccount(symbol)
    case "ticker" :
        data, err = e.GetTicker(symbol)
    case "depth" :
        data, err = e.GetDepth(symbol)
    case "trades" :
        data, err = e.GetTrades(symbol)
    case "trade" :
    ...
    default:
        ...

이 분할들은 반환된 데이터를 실행한 후, GMCP 플러그인 프로그램에서 응답해야 하는 구조로 작성하고, 관리자의 요청에 응답한다.

Go 언어 예시:

  defer func(){                                // 处理收尾工作 
        if e := recover(); e != nil {          // recover()函数用于捕获panic,e != nil即有错误发生
            if ee, ok := e.(error); ok {       // 类型推导,推导成功把ee.Error()赋值给e
                e = ee.Error()                 // 调用Error方法获取返回的错误字符串
            }
            ret = map[string]string{"error": fmt.Sprintf("%v", e)}
        }
        b, _ := json.Marshal(ret)              // 把本次调用获取的结果ret编码,赋值给b,写入响应指针
        w.Write(b)
        //fmt.Println("after w.Write the b is", string(b))     // 测试
    }()
  • #### 3 API 호출 종류

두 가지로 나눌 수 있습니다. 1. 서명할 필요가 없는 공공 인터페이스, 예를 들어:

GetTicker()

GetDepth()

GetTrades()

GetRecords(period)

  1. 서명이 필요한 사용자 인터페이스, 예를 들어:

BuySell

GetOrder(id)

GetOrders()

GetAccount()

CancelOrder(id) … 각 거래소의 서명 방식은 다를 수 있으며, 필요에 따라 구체적으로 작성해야 한다.

  • ### 4개, 발명자가 API 인터페이스 호출을 정량화할 때일반 프로토콜 플러그인그리고주인상호 작용하는 데이터 형식:

몇몇 발명가들의 API 인터페이스는 다음과 같다.GetName()GetLabel()그래서, 이 함수는일반 프로토콜 플러그인요청 보내세요. exchange.GetName()일반 플러그인 구성의 거래소가 호출될 때 “Exchange”로 돌아옵니다.

  • 1、GetTicker: 현재 상황을 파악하기 위한 자료입니다.

    주인이 함수에서 입력된 모든 함수는request~ 안에method다음으로:ticker

    관리자가 보낸 변수:request.Params.symbol: 관리자가 로봇 페이지에 따라 설정한 통화로 전송한다.

    호스트가 GNU의 플러그인을 요청할 때 주체가 가져갈 수 있는 데이터 형식 (JSON)

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "ticker",
        "params" :     {"symbol" : "ETH_BTC"},     // 以ETH_BTC交易对举例
    }
    

    최종적으로 호스트에 전송된 반환값 구조: ((즉, GMCP 플러그인이 거래소 인터페이스를 요청한 후 데이터가 호스트에 반환되는 형식)

    JSON 구조

    {
        "data": {
            "time": 1500793319499,  // 毫秒时间戳,整型
            "buy": 1000,            // 以下浮点型
            "sell": 1001,
            "last": 1005,
            "high": 1100,
            "low": 980,
            "vol": 523,
        }
    }
    
  • 2、GetRecords:거래소에서 제공하는 K선 데이터를 취득하기 위해 사용된다.

    이 함수에서 입력된 모든 함수들은request~ 안에method다음으로:records

    관리자가 보낸 변수:request.Params.period가치관exchange.GetRecords함수의 첫 번째 인수, 실제request.Params.period분수로 표현된 주기, 예를 들어 일기 주기 는60*24지금 바로1440request.Params.symbol: 관리자가 설정한 통화로 전송한다.

    호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "records",
        "params" :     {"symbol" : "ETH_BTC", "period" : "1440"},     // 以ETH_BTC交易对,K线周期为日线举例
    }
    

    최종적으로 호스트에게 전송된 반환 값 구조:

    JSON 구조

    {
        "data": [
                [1500793319, 1.1, 2.2, 3.3, 4.4, 5.5],         // "Time":1500793319000,"Open":1.1,"High":2.2,"Low":3.3,"Close":4.4,"Volume":5.5
                [1500793259, 1.01, 2.02, 3.03, 4.04, 5.05],
                ...
        ]
    }
    

    Go 언어 테스트 데이터:

    ret_records = []interface{}{
        [6]interface{}{1500793319, 1.1, 2.2, 3.3, 4.4, 5.5}, 
        [6]interface{}{1500793259, 1.01, 2.02, 3.03, 4.04, 5.05}
    }
    

    발명자의 양적 플랫폼Log보여주다records데이터:

    [
        {"Time":1500793319000,"Open":1.1,"High":2.2,"Low":3.3,"Close":4.4,"Volume":5.5},
        {"Time":1500793259000,"Open":1.01,"High":2.02,"Low":3.03,"Close":4.04,"Volume":5.05}
    ]
    

    참고: 1, 2차원 배열의 첫 번째 요소는int타입, 시간。 2 ∙ 관리자는 자동으로 시간。 곱하기 1000, 이상으로 볼 수 있습니다。

  • 3、GetDepth:거래소의 깊이 있는 정보를 얻으세요. (오더가 얇고, 하나 팔고, 둘 팔고, 하나 사고, 둘 사고…)

    이 함수에서 입력된 모든 함수들은request~ 안에method다음으로:depth

    관리자가 보낸 변수:request.Params.symbol: 관리자가 설정한 정책에 따라 송금됩니다.

    호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "depth",
        "params" :     {"symbol" : "ETH_BTC"},     // 以ETH_BTC交易对举例
    }
    

    최종적으로 호스트에게 전송된 반환 값 구조:

    JSON 구조

    {
        "data" : {
            "time" : 1500793319499,
            "asks" : [ [1000, 0.5], [1001, 0.23], [1004, 2.1], ... ],
            "bids" : [ [999, 0.25], [998, 0.8], [995, 1.4], ... ],
        }
    }
    
  • 4、GetTrades:거래소에서 제공하는 전체 거래소에서 일정 시간 동안의 거래 기록을 얻을 수 있습니다. (자기 거래 기록이 아닌)

    이 함수에서 입력된 모든 함수들은request~ 안에method다음으로:trades

    관리자가 보낸 변수:request.Params.symbol거래가 가능한 화폐는 다음과 같습니다.btc, 관리자가 설정한 정책에 따라 동전으로 전송한다.

    호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "trades",
        "params" :     {"symbol" : "ETH_BTC"},     // 以ETH_BTC交易对举例
    }
    

    최종적으로 호스트에게 전송된 반환 값 구조:

    JSON 구조

    { 
        "data": [
            {
                "id": 12232153,
                "time" : 1529919412968,
                "price": 1000,
                "amount": 0.5,
                "type": "buy",             // "buy"、"sell"
            },{
                "id": 12545664,
                "time" : 1529919412900,
                "price": 1001,
                "amount": 1,
                "type": "sell",
            },{
                ...
            }
        ]
    }
    
  • 5、GetAccount:계정 자산 정보를 얻을 수 있습니다.

    이 함수에서 입력된 모든 함수들은request~ 안에method다음으로:accounts

    관리자가 보낸 변수: ((주의하십시오! 일반적으로 계좌의 모든 자산을 얻습니다! 거래소 인터페이스를 따라 개별적으로 또는 총 자산 정보를 얻습니다.)

    호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "accounts",
        "params" :     {},                         
    }
    

    최종적으로 호스트에게 전송된 반환 값 구조:

    JSON 구조

    {
        "data": [
            {"currency": "btc", "free": 1.2, "frozen": 0.1},
            {"currency": "ltc", "free": 25, "frozen": 2.1},
            {"currency": "ltc", "free": 25, "frozen": 2.1},
            ...
        ],
        "raw" : {...}             // 可以写入插件访问交易所时,交易所返回的原始信息(response)
    }
    
  • 6、Buy、Sell:주문서, 주문거래.

    이 함수에서 입력된 모든 함수들은request~ 안에method다음으로:trade

    관리자가 보낸 변수:request.Params.type“부름에 따라 관리자”exchange.Buy또는exchange.Sell보내세요.request.Params.price전략에서 호출된:API함수의 첫 번째 인수인request.Params.amount전략에서 호출된:API함수의 두 번째 변수는request.Params.symbol: 관리자가 설정한 통화로 전송한다.

    호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "trade",
        "params" :     {
                           "symbol" : "ETH_BTC", 
                           "type" : "buy", 
                           "price" : "1000", 
                           "amount" : "1"
                       },                          // 以ETH_BTC交易对,"type":"buy"买请求,价格1000,数量1举例
    }
    

    최종적으로 호스트에게 전송된 반환 값 구조:

    JSON 구조

    {
        "data": {
            "id": 125456,      // 下单后返回的订单id
                               // 如果订单id是"asdf346sfasf532"这样的字符串形式
                               // 此处id也可以是字符串类型
        }
    }
    
  • 7、GetOrder:지정된 주문 번호의 주문 정보를 얻습니다.

    이 함수에서 입력된 모든 함수들은request~ 안에method다음으로:order

    관리자가 보낸 변수:request.Params.idrequest.Params.symbol

    호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "order",
        "params" :     {"symbol" : "ETH_BTC", "id" : "XXX"},     // 以ETH_BTC交易对,订单id为XXX举例,注意有些交易所的订单号是数字形式的订单ID,如123456,有些交易所的订单号是字符串形式的ID,如poimd55sdfheqxv,具体看交易所的订单ID格式
    }
    

    최종적으로 호스트에게 전송된 반환 값 구조:

    JSON 구조

    { 
        "data": {
            "id": 2565244,
            "amount": 0.15,
            "price": 1002,
            "status": "open",    // "open":挂起状态、"closed":完成关闭状态、"cancelled":已取消
            "deal_amount": 0,
            "type": "buy",       // "buy"、"sell"
            "avg_price": 0,      // 如果交易所没有提供,在处理时可以赋值为0
        }
    }
    
  • 8、GetOrders: 모든 미완성 주문 정보를 얻으십시오

    이 함수에서 입력된 모든 함수들은request메서드는 다음과 같습니다.orders

    관리자가 보낸 변수:request.Params.symbol

    호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "orders",
        "params" :     {"symbol" : "ETH_BTC"},     // 以ETH_BTC交易对举例
    }
    

    최종적으로 호스트에게 전송된 반환 값 구조:

    JSON 구조

    {
        "data": [{
            "id": 542156,
            "amount": 0.25,
            "price": 1005,
            "deal_amount": 0,
            "type": "buy",      // "buy"、"sell"
            "status": "open",   // "open"
        },{
            ...
        }]
    }
    
  • 9、CancelOrder: 지정된 주문 번호의 주문 위탁을 취소

    이 함수에서 입력된 모든 함수들은request~ 안에method다음으로:cancel

    관리자가 보낸 변수:request.Params.id: 문자열 타입, 정책 호출 API 함수의 첫 번째 변수,request.Params.symbol:btc (예: 예제) 관리자가 정책 설정에 따라 보낸 화폐

    호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "cancel",
        "params" :     {"symbol" : "ETH_BTC", "id" : "XXX"},     // 以ETH_BTC交易对,id为"XXX"(同GetOrder函数的参数id一样),举例
    }
    

    최종적으로 호스트에게 전송된 반환 값 구조:

    JSON 구조

    {
        "data": true,        // true or false
    }
    
  • 10、IO: 발명자의 양적 플랫폼을 호출하는 exchange.IO 함수

    이 함수에서 입력된 모든 함수들은request~ 안에method그래서__api_시작되는 메소드 이름:

    호스트가 GP 플러그인을 요청할 때 주체가 가져갈 수 있도록 요청하는 데이터 형식

    {
        "access_key" : "access_key",
        "secret_key" : "secret_key",
        "nonce" :      "1500793319499",            // 毫秒时间戳
        "method" :     "__api_XXX",                // XXX为具体交易所的API接口(不包含基地址)
        "params" :     {"borrow_id" : "123", "symbol" : "cny"},      // 具体是传入IO函数的参数
    }
    

    최종적으로 호스트에게 전송된 반환 값 구조:

    {
        "data": {...}       // 具体的接口调用的返回值
    }
    

    예를 들어, 전략 호출:

    var io_str = exchange.IO("api", "POST", "cancel_borrow", "symbol=cny&borrow_id=123")
    

    플러그인의 테스트 코드 (go 언어):

    fmt.Println("request.Method:", request.Method, "request.Params:", request.Params)
    

    플러그인 명령어: 2017/08/31 10:19:59 Running http://127.0.0.1:6666/DigitalAsset

    플러그인 명령에서 인쇄된 request.Method,request.Params 호스트가 보낸 요청의 몸체에서 데이터 해독 후의 요청:request.Method다음으로:__api_cancel_borrow 호스트가 보낸 요청의 몸체에서 데이터 해독 후의 요청:request.Params다음으로:{"borrow_id" : "123", "symbol" : "cny"}

    이러한 직접 액세스 거래소를 사용자 정의 할 수 있습니다API~의exchange.IO전화하라

    # 注意:
    # 在调用exchange.IO("api", "POST", "/api/v1/getAccount", "symbol=BTC_USDT")时,
    # 如果第二个参数不是POST,而是:exchange.IO("api", "GET", "/api/v1/getAccount", "symbol=BTC_USDT")
    # 是GET方法,这时在通用协议插件接受到的http请求中头部Http-Method中储存的才是GET,
    # 所以在通用协议处理IO函数实现时,需要参考以下范例代码:
    // tapiCall函数定义
    func (p *iStocksExchange) tapiCall(method string, params map[string]string, httpType string) (js *Json, err error) {
        ...
    }
    
    
    // 在OnPost函数中
    if strings.HasPrefix(request.Method, "__api_") {
        var js *Json
        js, err = e.tapiCall(request.Method[6:], request.Params, r.Header.Get("Http-Method"))
        ...
    }
    
  • Exchange.GetRawJSON에 대한 지원

    하층 자동 처리exchange.GetRawJSON플러그인에서 실행할 필요가 없습니다.

  • 이 사이트는 Exchange.Go를 지원합니다

    하층 자동 처리exchange.Go플러그인에서 처리할 필요가 없습니다.

    var beginTime = new Date().getTime()
    var ret = exchange.Go("GetDepth")
    var endTime = new Date().getTime()
    Log(endTime - beginTime, "#FF0000")
    
    
    // Sleep(2000)
    beginTime = new Date().getTime()
    Log(exchange.GetTicker())
    endTime = new Date().getTime()
    Log(endTime - beginTime, "#FF0000")
    var depth = ret.wait()
    Log("depth:", depth)
    

    발명자의 양적 거래 플랫폼 범용 프로토콜은 맞춤형 거래소에 연결됩니다.

    발명자의 양적 거래 플랫폼 범용 프로토콜은 맞춤형 거래소에 연결됩니다.

    # 注意:使用exchange.Go在wait的时候如果指定了超时时间, 
    #      一定要确保获取到最终的数据,这样申请的并发线程才能回收。
    
  • 임기 함수에 대한 지원

    플러그인 프로그램에서 리버, 계약 코드, 주문 방향 등을 설정하는 특정 처리가 필요하며, 로컬 변수 레코드를 설정할 수 있으며, 포지션을 얻기 위해서는 거래소 인터페이스를 방문하여 원본 데이터를 얻고 FMZ 플랫폼에서 정의된 포지션 구조로 처리하여 반환해야합니다. 다음의 함수를 호출할 때 플러그인 프로그램이 수신하는Rpc요청 형식은 다른 인터페이스와 약간 다릅니다.RpcRequest이 형식의 주요 차이점은 params의 값이 복합 구조라는 것이다.

    • SetContractType
      계약 코드를 설정하세요.
      {"access_key":"123","method":"io","nonce":1623307269528738000,"params":{"args":["quarter"],"code":2},"secret_key":"123"}
    
    • SetDirection 퓨어스 (Forex) 를 설정하고
      {"access_key":"123","method":"io","nonce":1623308734966484000,"params":{"args":["closesell"],"code":1},"secret_key":"123"}
    
    • SetMarginLevel 선물 리버를 설정하세요.
      {"access_key":"123","method":"io","nonce":1623308734966939000,"params":{"args":[12],"code":0},"secret_key":"123"}
    
    • GetPosition 미래에셋대우를 확보하세요 언제exchange.GetPosition()전화할 때:
      {"access_key":"123","method":"io","nonce":1623308734967442000,"params":{"args":[],"code":3},"secret_key":"123"}
    

    언제exchange.GetPosition("swap")전화할 때:

      {"access_key":"123","method":"io","nonce":1623308734967442000,"params":{"args":["swap"],"code":3},"secret_key":"123"}
    

/*
GOOS=linux GOARCH=amd64 go build -ldflags '-s -w -extldflags -static' rest_bitgo.go
*/
package main

import (
    "bytes"
    "crypto/md5"
    "encoding/hex"
    "encoding/json"
    "errors"
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "sort"
    "strconv"
    "strings"
    "time"
)

func toFloat(s interface{}) float64 {
    var ret float64
    switch v := s.(type) {
    case float64:
        ret = v
    case float32:
        ret = float64(v)
    case int64:
        ret = float64(v)
    case int:
        ret = float64(v)
    case int32:
        ret = float64(v)
    case string:
        ret, _ = strconv.ParseFloat(strings.TrimSpace(v), 64)
    }
    return ret
}

func float2str(i float64) string {
    return strconv.FormatFloat(i, 'f', -1, 64)
}

func toInt64(s interface{}) int64 {
    var ret int64
    switch v := s.(type) {
    case int:
        ret = int64(v)
    case float64:
        ret = int64(v)
    case bool:
        if v {
            ret = 1
        } else {
            ret = 0
        }
    case int64:
        ret = v
    case string:
        ret, _ = strconv.ParseInt(strings.TrimSpace(v), 10, 64)
    }
    return ret
}

func toString(s interface{}) string {
    var ret string
    switch v := s.(type) {
    case string:
        ret = v
    case int64:
        ret = strconv.FormatInt(v, 10)
    case float64:
        ret = strconv.FormatFloat(v, 'f', -1, 64)
    case bool:
        ret = strconv.FormatBool(v)
    default:
        ret = fmt.Sprintf("%v", s)
    }
    return ret
}

type Json struct {
    data interface{}
}

func NewJson(body []byte) (*Json, error) {
    j := new(Json)
    err := j.UnmarshalJSON(body)
    if err != nil {
        return nil, err
    }
    return j, nil
}

func (j *Json) UnmarshalJSON(p []byte) error {
    return json.Unmarshal(p, &j.data)
}

func (j *Json) Get(key string) *Json {
    m, err := j.Map()
    if err == nil {
        if val, ok := m[key]; ok {
            return &Json{val}
        }
    }
    return &Json{nil}
}

func (j *Json) CheckGet(key string) (*Json, bool) {
    m, err := j.Map()
    if err == nil {
        if val, ok := m[key]; ok {
            return &Json{val}, true
        }
    }
    return nil, false
}

func (j *Json) Map() (map[string]interface{}, error) {
    if m, ok := (j.data).(map[string]interface{}); ok {
        return m, nil
    }
    return nil, errors.New("type assertion to map[string]interface{} failed")
}

func (j *Json) Array() ([]interface{}, error) {
    if a, ok := (j.data).([]interface{}); ok {
        return a, nil
    }
    return nil, errors.New("type assertion to []interface{} failed")
}

func (j *Json) Bool() (bool, error) {
    if s, ok := (j.data).(bool); ok {
        return s, nil
    }
    return false, errors.New("type assertion to bool failed")
}

func (j *Json) String() (string, error) {
    if s, ok := (j.data).(string); ok {
        return s, nil
    }
    return "", errors.New("type assertion to string failed")
}

func (j *Json) Bytes() ([]byte, error) {
    if s, ok := (j.data).(string); ok {
        return []byte(s), nil
    }
    return nil, errors.New("type assertion to []byte failed")
}

func (j *Json) Int() (int, error) {
    if f, ok := (j.data).(float64); ok {
        return int(f), nil
    }

    return -1, errors.New("type assertion to float64 failed")
}

func (j *Json) MustArray(args ...[]interface{}) []interface{} {
    var def []interface{}

    switch len(args) {
    case 0:
    case 1:
        def = args[0]
    default:
        log.Panicf("MustArray() received too many arguments %d", len(args))
    }

    a, err := j.Array()
    if err == nil {
        return a
    }

    return def
}

func (j *Json) MustMap(args ...map[string]interface{}) map[string]interface{} {
    var def map[string]interface{}

    switch len(args) {
    case 0:
    case 1:
        def = args[0]
    default:
        log.Panicf("MustMap() received too many arguments %d", len(args))
    }

    a, err := j.Map()
    if err == nil {
        return a
    }

    return def
}

func (j *Json) MustString(args ...string) string {
    var def string

    switch len(args) {
    case 0:
    case 1:
        def = args[0]
    default:
        log.Panicf("MustString() received too many arguments %d", len(args))
    }

    s, err := j.String()
    if err == nil {
        return s
    }

    return def
}

func (j *Json) MustInt64() int64 {
    var ret int64
    var err error
    switch v := j.data.(type) {
    case int:
        ret = int64(v)
    case int64:
        ret = v
    case float64:
        ret = int64(v)
    case string:
        if ret, err = strconv.ParseInt(v, 10, 64); err != nil {
            panic(err)
        }
    default:
        ret = 0
        //panic("type assertion to int64 failed")
    }
    return ret
}

func (j *Json) MustFloat64() float64 {
    var ret float64
    var err error
    switch v := j.data.(type) {
    case int:
        ret = float64(v)
    case int64:
        ret = float64(v)
    case float64:
        ret = v
    case string:
        v = strings.Replace(v, ",", "", -1)
        if ret, err = strconv.ParseFloat(v, 64); err != nil {
            panic(err)
        }
    default:
        ret = 0
        //panic("type assertion to float64 failed")
    }
    return ret
}

type iBitgo struct {
    accessKey     string
    secretKey     string
    currency      string
    opCurrency    string
    baseCurrency  string
    secret        string
    secretExpires int64
    apiBase       string
    step          int64
    newRate       float64
    timeout       time.Duration
    timeLocation  *time.Location
}

type MapSorter []Item

type Item struct {
    Key string
    Val string
}

func NewMapSorter(m map[string]string) MapSorter {
    ms := make(MapSorter, 0, len(m))

    for k, v := range m {
        if strings.HasPrefix(k, "!") {
            k = strings.Replace(k, "!", "", -1)
        }
        ms = append(ms, Item{k, v})
    }

    return ms
}

func (ms MapSorter) Len() int {
    return len(ms)
}

func (ms MapSorter) Less(i, j int) bool {
    //return ms[i].Val < ms[j].Val // 按值排序
    return ms[i].Key < ms[j].Key // 按键排序
}

func (ms MapSorter) Swap(i, j int) {
    ms[i], ms[j] = ms[j], ms[i]
}

func encodeParams(params map[string]string, escape bool) string {
    ms := NewMapSorter(params)
    sort.Sort(ms)

    v := url.Values{}
    for _, item := range ms {
        v.Add(item.Key, item.Val)
    }
    if escape {
        return v.Encode()
    }
    var buf bytes.Buffer
    keys := make([]string, 0, len(v))
    for k := range v {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    for _, k := range keys {
        vs := v[k]
        prefix := k + "="
        for _, v := range vs {
            if buf.Len() > 0 {
                buf.WriteByte('&')
            }
            buf.WriteString(prefix)
            buf.WriteString(v)
        }
    }
    return buf.String()
}

func newBitgo(accessKey, secretKey string) *iBitgo {
    s := new(iBitgo)
    s.accessKey = accessKey
    s.secretKey = secretKey
    s.apiBase = "https://www.bitgo.cn"
    s.timeout = 20 * time.Second
    s.timeLocation = time.FixedZone("Asia/Shanghai", 8*60*60)

    return s
}

func (p *iBitgo) apiCall(method string) (*Json, error) {
    req, err := http.NewRequest("POST", fmt.Sprintf("%s/appApi.html?%s", p.apiBase, method), nil)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    return NewJson(b)
}

func (p *iBitgo) GetTicker(symbol string) (ticker interface{}, err error) {
    var js *Json
    js, err = p.apiCall("action=market&symbol=" + symbol)
    if err != nil {
        return
    }
    dic := js.Get("data")
    ticker = map[string]interface{}{
        "time": js.Get("time").MustInt64(),
        "buy":  dic.Get("buy").MustFloat64(),
        "sell": dic.Get("sell").MustFloat64(),
        "last": dic.Get("last").MustFloat64(),
        "high": dic.Get("high").MustFloat64(),
        "low":  dic.Get("low").MustFloat64(),
        "vol":  dic.Get("vol").MustFloat64(),
    }
    return
}

func (p *iBitgo) GetDepth(symbol string) (depth interface{}, err error) {
    var js *Json
    js, err = p.apiCall("action=depth&symbol=" + symbol)
    if err != nil {
        return
    }
    dic := js.Get("data")

    asks := [][2]float64{}
    bids := [][2]float64{}

    for _, pair := range dic.Get("asks").MustArray() {
        arr := pair.([]interface{})
        asks = append(asks, [2]float64{toFloat(arr[0]), toFloat(arr[1])})
    }

    for _, pair := range dic.Get("bids").MustArray() {
        arr := pair.([]interface{})
        bids = append(bids, [2]float64{toFloat(arr[0]), toFloat(arr[1])})
    }
    depth = map[string]interface{}{
        "time": js.Get("time").MustInt64(),
        "asks": asks,
        "bids": bids,
    }
    return
}

func (p *iBitgo) GetTrades(symbol string) (trades interface{}, err error) {
    var js *Json
    js, err = p.apiCall("action=trades&symbol=" + symbol)
    if err != nil {
        return
    }
    dic := js.Get("data")
    items := []map[string]interface{}{}
    for _, pair := range dic.MustArray() {
        item := map[string]interface{}{}
        arr := pair.(map[string]interface{})
        item["id"] = toInt64(arr["id"])
        item["price"] = toFloat(arr["price"])
        item["amount"] = toFloat(arr["amount"])
        // trade.Time = toInt64(arr["time"]) * 1000
        if toString(arr["en_type"]) == "bid" {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }
        items = append(items, item)
    }
    trades = items
    return
}

func (p *iBitgo) GetRecords(step int64, symbol string) (records interface{}, err error) {
    var js *Json
    js, err = p.apiCall(fmt.Sprintf("action=kline&symbol=%s&step=%d", symbol, step*60))
    if err != nil {
        return
    }
    items := []interface{}{}
    for _, pair := range js.Get("data").MustArray() {
        arr := pair.([]interface{})
        if len(arr) < 6 {
            err = errors.New("response format error")
            return
        }
        item := [6]interface{}{}
        item[0] = toInt64(arr[0])
        item[1] = toFloat(arr[1])
        item[2] = toFloat(arr[2])
        item[3] = toFloat(arr[3])
        item[4] = toFloat(arr[4])
        item[5] = toFloat(arr[5])

        items = append(items, item)
    }
    records = items
    return
}

func (p *iBitgo) tapiCall(method string, params map[string]string) (js *Json, err error) {
    if params == nil {
        params = map[string]string{}
    }
    params["api_key"] = p.accessKey
    h := md5.New()
    h.Write([]byte(encodeParams(params, false) + "&secret_key=" + p.secretKey))
    params["sign"] = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
    params["action"] = method
    qs := encodeParams(params, false)

    req, err := http.NewRequest("POST", fmt.Sprintf("%s/appApi.html?%s", p.apiBase, qs), nil)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    js, err = NewJson(b)
    if js != nil {
        if code := js.Get("code").MustInt64(); code != 200 {
            s := js.Get("msg").MustString()
            if s == "" {
                s = fmt.Sprintf("%v", toString(js.data))
            }
            return nil, errors.New(s)
        }
    }
    return js, err
}

func (p *iBitgo) GetAccount(symbol string) (account interface{}, err error) {
    var js *Json
    js, err = p.tapiCall("userinfo", nil)
    if err != nil {
        return
    }
    mp := js.Get("data")
    assets := map[string]map[string]interface{}{}
    for k := range mp.MustMap() {
        dic := mp.Get(k)
        if k == "free" {
            for c := range dic.MustMap() {
                if _, ok := assets[c]; !ok {
                    assets[c] = map[string]interface{}{}
                }
                assets[c]["currency"] = c
                assets[c]["free"] = dic.Get(c).MustFloat64()
            }
        } else if k == "frozen" {
            for c := range dic.MustMap() {
                if _, ok := assets[c]; !ok {
                    assets[c] = map[string]interface{}{}
                }
                assets[c]["currency"] = c
                assets[c]["frozen"] = dic.Get(c).MustFloat64()
            }
        }
    }
    accounts := []map[string]interface{}{}
    for _, pair := range assets {
        accounts = append(accounts, pair)
    }

    account = accounts
    return
}

func (p *iBitgo) Trade(side string, price, amount float64, symbol string) (orderId interface{}, err error) {
    var js *Json
    js, err = p.tapiCall("trade", map[string]string{
        "symbol": symbol,
        "type":   side,
        "price":  float2str(price),
        "amount": float2str(amount),
    })
    if err != nil {
        return
    }
    orderId = map[string]int64{"id": js.Get("orderId").MustInt64()}
    return
}

func (p *iBitgo) GetOrders(symbol string) (orders interface{}, err error) {
    var js *Json
    js, err = p.tapiCall("entrust", map[string]string{"symbol": symbol})
    if err != nil {
        return
    }
    items := []map[string]interface{}{}
    for _, ele := range js.Get("data").MustArray() {
        mp := ele.(map[string]interface{})
        item := map[string]interface{}{}
        item["id"] = toInt64(mp["id"])
        item["amount"] = toFloat(mp["count"])
        if _, ok := mp["prize"]; ok {
            item["price"] = toFloat(mp["prize"])
        } else {
            item["price"] = toFloat(mp["price"])
        }
        item["deal_amount"] = toFloat(mp["success_count"])

        if toInt64(mp["type"]) == 0 {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }
        item["status"] = "open"
        items = append(items, item)
    }
    return items, nil
}

func (p *iBitgo) GetOrder(orderId int64, symbol string) (order interface{}, err error) {
    var js *Json
    js, err = p.tapiCall("order", map[string]string{"id": toString(orderId)})
    if err != nil {
        return
    }

    found := false
    item := map[string]interface{}{}
    for _, ele := range js.Get("data").MustArray() {
        mp := ele.(map[string]interface{})
        if toInt64(mp["id"]) != orderId {
            continue
        }
        item["id"] = toInt64(mp["id"])
        item["amount"] = toFloat(mp["count"])
        if _, ok := mp["prize"]; ok {
            item["price"] = toFloat(mp["prize"])
        } else {
            item["price"] = toFloat(mp["price"])
        }
        item["deal_amount"] = toFloat(mp["success_count"])

        if toInt64(mp["type"]) == 0 {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }
        switch toInt64(mp["status"]) {
        case 1, 2:
            item["status"] = "open"
        case 3:
            item["status"] = "closed"
        case 4:
            item["status"] = "cancelled"
        }
        found = true
        break
    }
    if !found {
        return nil, errors.New("order not found")
    }
    return item, nil
}

func (p *iBitgo) CancelOrder(orderId int64, symbol string) (ret bool, err error) {
    _, err = p.tapiCall("cancel_entrust", map[string]string{"id": strconv.FormatInt(orderId, 10)})
    if err != nil {
        return
    }
    ret = true
    return
}

type RpcRequest struct {        // 结构体里的字段首字母必须大写,否则无法正常解析,结构体有导出和未导出,大写字母开头为导出。
                                // 在Unmarshal的时候会  根据 json 匹配查找该结构体的tag, 所以此处需要修饰符
    AccessKey string            `json:"access_key"`
    SecretKey string            `json:"secret_key"`
    Nonce     int64             `json:"nonce"`
    Method    string            `json:"method"`
    Params    map[string]string `json:"params"`
}

func OnPost(w http.ResponseWriter, r *http.Request) {
    var ret interface{}
    defer func() {
        if e := recover(); e != nil {
            if ee, ok := e.(error); ok {
                e = ee.Error()
            }
            ret = map[string]string{"error": fmt.Sprintf("%v", e)}
        }
        b, _ := json.Marshal(ret)
        w.Write(b)
    }()

    b, err := ioutil.ReadAll(r.Body)
    if err != nil {
        panic(err)
    }
    var request RpcRequest
    err = json.Unmarshal(b, &request)
    if err != nil {
        panic(err)
    }
    e := newBitgo(request.AccessKey, request.SecretKey)
    symbol := request.Params["symbol"]
    if s := request.Params["access_key"]; len(s) > 0 {
        e.accessKey = s
    }
    if s := request.Params["secret_key"]; len(s) > 0 {
        e.secretKey = s
    }
    if symbolIdx, ok := map[string]int{
        "btc":  1,
        "ltc":  2,
        "etp":  3,
        "eth":  4,
        "etc":  5,
        "doge": 6,
        "bec":  7,
    }[strings.Replace(strings.ToLower(symbol), "_cny", "", -1)]; ok {
        symbol = toString(symbolIdx)
    }
    var data interface{}
    switch request.Method {
    case "ticker":
        data, err = e.GetTicker(symbol)
    case "depth":
        data, err = e.GetDepth(symbol)
    case "trades":
        data, err = e.GetTrades(symbol)
    case "records":
        data, err = e.GetRecords(toInt64(request.Params["period"]), symbol)
    case "accounts":
        data, err = e.GetAccount(symbol)
    case "trade":
        side := request.Params["type"]
        if side == "buy" {
            side = "0"
        } else {
            side = "1"
        }
        price := toFloat(request.Params["price"])
        amount := toFloat(request.Params["amount"])
        data, err = e.Trade(side, price, amount, symbol)
    case "orders":
        data, err = e.GetOrders(symbol)
    case "order":
        data, err = e.GetOrder(toInt64(request.Params["id"]), symbol)
    case "cancel":
        data, err = e.CancelOrder(toInt64(request.Params["id"]), symbol)
    default:
        if strings.HasPrefix(request.Method, "__api_") {
            data, err = e.tapiCall(request.Method[6:], request.Params)
        } else {
            panic(errors.New(request.Method + " not support"))
        }
    }
    if err != nil {
        panic(err)
    }
    ret = map[string]interface{}{
        "data": data,
    }

    return
}

func main() {
    var addr = flag.String("b", "127.0.0.1:6666", "bind addr")
    flag.Parse()
    if *addr == "" {
        flag.Usage()
        return
    }
    basePath := "/exchange"
    log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...")
    http.HandleFunc(basePath, OnPost)
    http.ListenAndServe(*addr, nil)
}