وسائل لوڈ ہو رہے ہیں... لوڈنگ...

مثال کے طور پر ایک انوینٹر جنرل پروٹوکول معاہدے تک رسائی

مصنف:ایجاد کاروں کی مقدار - خواب, تخلیق: 2021-09-14 15:00:01, تازہ کاری: 2023-09-20 09:12:09

img

مثال کے طور پر ایک انوینٹر جنرل پروٹوکول معاہدے تک رسائی

حالیہ صارفین کی گفتگوAOFEXاس تبادلے کے لئے، FMZ پر ابھی تک ایک مثال نہیں ہے کہ کس طرح ایک معاہدے کے تبادلے کے REST انٹرفیس سے رابطہ قائم کرنے کے لئے؛ یہ مضمون رسائی کے لئے ہے.AOFEXایک مثال کے طور پر، ایک معاہدہ ایکسچینج تک رسائی حاصل کرنے کا طریقہ بیان کریں۔

ایف ایم زیڈ پر فوری تبادلے اور فیوچر ایکسچینج کے مابین فرق ہے۔ مثال کے طور پر ، ہم جانتے ہیں کہ تین بڑی جگہیں ہیں جہاں فوری تجارت اور معاہدہ کی تجارت ہوتی ہے۔ ان مختلف مارکیٹوں کے API انٹرفیس بھی مختلف ہیں ، اور یہاں تک کہ کچھ API سسٹم بھی مکمل طور پر الگ الگ ہیں۔ لہذا ایف ایم زیڈ پر ان تبادلے کو پیک کرتے وقت فوری اور فیوچر کا فرق ہوتا ہے۔

ایف ایم زیڈ پلیٹ فارم کمیونٹی میں ایف ایم زیڈ کا استعمال کرتے ہوئے جنرل پروٹوکول کو پیک کرنے کے لئے بہت ساری مثالیں موجود ہیں ، لیکن ایک معاہدہ ایکسچینج کو پیک کرنے کے لئے ابھی تک ایک مکمل مثال موجود نہیں ہے۔ تاہم ، پیکیجڈ فیوچر ورژن کا جنرل پروٹوکول پلگ ان بنیادی طور پر اسی طرح کا ہے جیسا کہ اسٹاک ہے۔ صرف چند انٹرفیس ہیں۔

ایف ایم زیڈ جنرل پروٹوکول دستاویزات:https://www.fmz.com/bbs-topic/1052دستاویزات کے ساتھ ایک فوری طور پر تبادلہ کے لئے عام پروٹوکول تک رسائی حاصل کریں DEMO

انٹرفیس جس میں فوری طور پر تبادلہ کرنے والے اشیاء اور فیوچر ایکسچینج اشیاء کو پیک کرنے کی ضرورت ہوتی ہے

فوری طور پر تبادلہ کرنے والے اشیاء کا بنیادی انٹرفیس

  • exchange.GetTicker ((() ٹِک مارکیٹ کے اعداد و شمار حاصل کرنے کے لئے ، فوری طور پر مستقبل کے پیکیجوں کی بھی ضرورت ہے۔

  • exchange.GetDepth (() اس کے علاوہ ، یہ بھی ممکن ہے کہ آپ کے پاس آرڈر کے اعداد و شمار ہیں ، اور آپ کو فوری طور پر مستقبل کے لئے بھی معلومات مل سکتی ہیں۔

  • exchange.GetTrades ((() اس کے علاوہ، آپ کو ایک بار پھر ایک بار پھر ایک بار پھر ایک بار پھر ایک بار پھر ایک بار پھر ایک بار پھر ایک بار پھر ایک بار پھر ایک بار پھر ایک بار پھر.

  • exchange.GetRecords ((() اس کے علاوہ ، یہ بھی ممکن ہے کہ آپ کے پاس کچھ ڈیٹا موجود ہو ، جیسے کہ آپ کے پاس K لائن کے اعداد و شمار ہیں ، اور آپ کے پاس فوری مستقبل ہے۔

  • exchange.GetAccount ((() اس کے علاوہ ، آپ کو اپنے اکاؤنٹ کے اثاثوں کا ڈیٹا حاصل کرنے کی ضرورت ہے۔

  • exchange.Buy ((() اس کے علاوہ ، یہ بھی کہا جاتا ہے کہ یہ ایک بہت بڑا مسئلہ ہے ، لیکن اس کا حل یہ ہے کہ یہ ایک بہت بڑا مسئلہ ہے۔

  • exchange.Sell ((() اس کے علاوہ ، یہ بھی کہا جاتا ہے کہ یہ ایک بہت ہی عام چیز ہے ، لیکن یہ ایک بہت ہی عام چیز ہے۔

  • exchange.GetOrder ((() اس کے علاوہ ، آپ کو اپنے آرڈر کے اعداد و شمار کو حاصل کرنے کی ضرورت ہوگی ، جس میں آپ کے آرڈر کا آئی ڈی درج کیا گیا ہے۔

  • exchange.GetOrders ((() اس وقت کی سرگرمیوں میں سے کسی بھی شیڈول کو حاصل کرنے کے لئے ، فوری مستقبل کے ساتھ بھی۔

  • exchange.CancelOrder (انگریزی میں) اس کے علاوہ ، آپ کو اپنے آئی ڈی کے ساتھ آرڈر منسوخ کرنے کی ضرورت ہے۔

فیوچر ایکسچینج آبجیکٹ کو ان انٹرفیسز کے علاوہ اضافی انٹرفیس افعال کی ضرورت ہوتی ہے جن کے لئے فیوچر ایکسچینج آبجیکٹ کو پیک کرنا ضروری ہے۔

  • exchange.SetMarginLevel ((() موجودہ قسم کے لئے لیوریج مقرر کریں۔

  • exchange.SetDirection (() موجودہ قسم کی تجارت کی سمت مقرر کریں: کھلی پوزیشن / کھلی پوزیشن / فلیش پوزیشن / فلیش پوزیشن ؛

  • exchange.SetContractType (() موجودہ معاہدے کا کوڈ سیٹ کریں۔ کیونکہ مستقبل میں مستقل معاہدے ہیں۔swap◄ تبادلوں کی شرحquarterاس کے علاوہ ، ایف ایم زیڈ کے پاس اپنے اپنے معاہدے کے کوڈ ہیں ، جو ایف ایم زیڈ اے پی آئی دستاویزات سے متعلق ہیں۔ پیکیجنگ کے وقت بھی ان کی پیروی کرنے کی ضرورت ہے ، ورنہ پرانی پالیسیاں کام نہیں کرسکتی ہیں۔

  • exchange.GetPosition ((() موجودہ قسم کے ہولڈنگ ڈیٹا حاصل کریں۔ یہاں آپ دیکھ سکتے ہیں کہ فوری طور پر کوئی ہولڈنگ تصور نہیں ہے ، فوری طور پر صرف حساب کتاب میں تبدیلی کے ذریعہ منطقی ہولڈنگ کا حساب لگایا جاسکتا ہے۔ لیکن مستقبل میں ہولڈنگ ہے۔

پالیسی میں کال کرنے کے لئے فیوچر خصوصی انٹرفیس جب منتظمین کو جنرل پروٹوکول پلگ ان کی درخواست میں جسم کے اعداد و شمار بھیجنے کے لئے

مثال کے طور پر AOFEX ایکسچینج کے ساتھ ، اگر FMZ پر جنرل پروٹوکول کو ترتیب دینے والے ایکسچینج آبجیکٹ میں ، API KEY کو پُر کیا جائے گا:

  • تک رسائی کی چابی: 212f54a1-1c88-1bf5-54a1-f7bf52b3256c
  • secretKey: 7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs

img

ایف ایم زیڈ پر جنرل پروٹوکول کی ترتیب کے صفحے پرaccessKeyاورsecretKeyان پٹ باکس، جنرل پروٹوکول ایکسچینج آبجیکٹ کی تشکیل کے بعد، جنرل پروٹوکول پلگ ان پروگرام چل رہا ہے جب درخواست میں موصول ہونے والے جسم کے JSON فارمیٹ کے اعداد و شمار کیaccessKeyاور اس کے بعد آپ کو ایک بار پھر ایک بار پھر ایک بار پھر ایک بار212f54a1-1c88-1bf5-54a1-f7bf52b3256csecret_keyاور اس کے بعد آپ کو ایک بار پھر ایک بار پھر ایک بار پھر ایک بار7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs

مندرجہ ذیل انٹرفیس پر کال کرتے وقت، منتظمین جنرل پروٹوکول پلگ ان کو RPC درخواستیں بھیجتے ہیں:

  • جب پالیسی میں بلایا جائےexchange.SetMarginLevel(10)اس کے بعد ، آپ کو اپنے مطلوبہ مواد کو دوبارہ ترتیب دینے کی ضرورت ہے۔

    {
        "access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
        "method":"io",
        "nonce":1631858961289247000,
        "params":{"args":[10],"code":0},   
        "secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
    }
    

    ایکسچینج.سیٹ مارجن لیول ((10) کو کال کریں ، اور پیرامیٹرز کو 10 میں منتقل کریں۔

  • جب پالیسی میں بلایا جائےexchange.SetDirection("buy")اس کے بعد ، آپ کو اپنے مطلوبہ مواد کو دوبارہ ترتیب دینے کی ضرورت ہے۔

    {
        "access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
        "method":"io",
        "nonce":1631860438946922000,
        "params":{"args":["buy"],"code":1},  
        "secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
    }
    

    ایکسچینج.سیٹ ڈائریکشن ((buy)) کو کال کریں ، اور پیرامیٹرز کو "خرید" میں منتقل کریں۔

  • جب پالیسی میں بلایا جائےexchange.SetContractType("swap")اس کے بعد ، آپ کو اپنے مطلوبہ مواد کو دوبارہ ترتیب دینے کی ضرورت ہے۔

    {
        "access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
        "method":"io",
        "nonce":1631860847525039000,
        "params":{"args":["swap"],"code":2},   
        "secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
    }
    

    تبادلہ.سیٹ معاہدہ کی قسم (swap ٹن) کو کال کریں ، اور پیرامیٹرز کو "سواپ" میں منتقل کریں۔

  • جب پالیسی میں بلایا جائےexchange.GetPosition()اس کے بعد ، آپ کو اپنے مطلوبہ مواد کو دوبارہ ترتیب دینے کی ضرورت ہے۔

    {
        "access_key":"212f54a1-1c88-1bf5-54a1-f7bf52b3256c",
        "method":"io",
        "nonce":1631860996119505000,
        "params":{"args":[],"code":3},  
        "secret_key":"7RJPKpBJMBkUL87RJPKpkULJPSUpsaKpUL83ysDs"
    }
    

    جب exchange.GetPosition ((() کو بلایا جاتا ہے تو کوئی پیرامیٹر منتقل نہیں ہوتا ہے۔

مندرجہ بالا درخواست باڈی میں JSON ڈیٹا کو دیکھ کر پتہ چلتا ہے:

  • جب کسی فاریکس ایکسچینج کے آبجیکٹ کے مخصوص فنکشن کو بلایا جاتا ہے تو ، درخواست کرنے والے جسم میںmethodاور آپ کو اس کے بارے میں کیا پتہ ہے؟io
  • ان افعال کو الگ کرنے کے لئےparamsاس فیلڈ میںcodeیہ فیصلہ:
    • codeکے لئے0ہاںSetMarginLevel
    • codeکے لئے1ہاںSetDirection
    • codeکے لئے2ہاںSetContractType
    • codeکے لئے3ہاںGetPosition
  • یہ مستقبل کے تبادلے کی اشیاء کے لئے مخصوص افعال ہیں جب ان کو بلایا جاتا ہے تو ، درخواست کرنے والے جسم میں تمام پیرامیٹرز کو منتقل کیا جاتا ہے۔paramsفیلڈargsمڈل۔

Go زبان کے پلگ ان کی مثالوں کے مقابلے میں، آپ کو اس کے لئے کچھ وقت کی ضرورت ہوگی.OnPostاس فنکشن میں توسیع کریں: یہاں ایک کوڈ ہے:افعال کو بڑھانے کے لئے فیوچر ایکسچینج اشیاء کی ضرورت ہوتی ہے"تبصرے کی جگہ"

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 := newZG(request.AccessKey, request.SecretKey)

    var symbol string 
    if _, ok := request.Params["symbol"]; ok {
        symbol = strings.ToUpper(request.Params["symbol"].(string))
    }

    var data interface{}
    switch request.Method {
    case "ticker":
        data, err = e.GetTicker(symbol, "GET")                                        
    case "depth":
        data, err = e.GetDepth(symbol, "GET")
    case "trades":
        data, err = e.GetTrades(symbol, "GET")
    case "records":
        data, err = e.GetRecords(toInt64(request.Params["period"]), symbol, "GET")    
    case "accounts":
        data, err = e.GetAccount(symbol, "GET")
    case "trade":
        side := request.Params["type"].(string)
        if side == "buy" {
            side = "BUY"
        } else {
            side = "SELL"
        }
        price := toFloat(request.Params["price"])
        amount := toFloat(request.Params["amount"])
        data, err = e.Trade(side, price, amount, symbol, "POST")
    case "orders":
        data, err = e.GetOrders(symbol, "POST")
    case "order":
        data, err = e.GetOrder(toString(request.Params["id"]), symbol, "POST")
    case "cancel":
        data, err = e.CancelOrder(toString(request.Params["id"]), symbol, "POST")
    default:
        if strings.HasPrefix(request.Method, "__api_") {
            params := map[string]interface{}{}
            for k, v := range request.Params {
                params[k] = toString(v)
            }
            data, err = e.tapiCall(request.Method[6:], params, "GET")
        } else if request.Method == "io" {              // 扩展期货交易所对象需要的函数
            code := toString(request.Params["code"])
            if code == "0" {            
                // 处理SetMarginLevel
                // ...
            } else if code == "1" {
                // 处理SetDirection
                // ...
            } else if code == "2" {     
                // 处理SetContractType
                // ...
            } else if code == "3" {     
                // 处理GetPosition
                // ...
            } else {
                panic(errors.New(request.Method + " not support"))    
            }
        } else {
            panic(errors.New(request.Method + " not support"))
        }
    }
    if err != nil {
        panic(err)
    }
    ret = map[string]interface{}{
        "data": data,
    }
    return
}

SetMarginLevel/SetDirection/SetContractTypeتین افعال کو لفظی معنی میں دیکھا جاسکتا ہے جو موجودہ تجارت کی قسم کو ترتیب دینے کے لئے استعمال کیا جاتا ہے۔SetDirection/SetContractTypeڈیزائن کے لحاظ سے ، یہ ہے کہ موجودہ آرڈر کی سمت کو ریکارڈ کرنے کے لئے مقامی متغیر کو ترتیب دیں (یعنی آرڈر دیتے وقت اس ترتیب کو پڑھنا ، یہ جاننا کہ کس سمت میں آرڈر کرنا ہے ، کیونکہ مستقبل میں دو سمتیں ہیں: زیادہ اور خالی ، لہذا فرق کرنے کی ضرورت ہے) اور موجودہ معاہدے کا کوڈ (حاصل کرنے کے عمل ، آرڈر وغیرہ کے عمل میں واضح طور پر پوچھنا ہے کہ کون سا معاہدہ ہے) ۔

SetMarginLevelلیورجنگ کے طریقہ کار کے مطابق مخصوص ڈیزائن کی ضرورت ہے۔ (۱، لیورجنگ پیرامیٹرز کو مندرجہ ذیل انٹرفیس میں بطور پیرامیٹر منتقل کیا جاتا ہے۔ (۲، تمام سیٹ لیورجنگ انٹرفیس پر تجارت) ۔

GetPositionموجودہ قسم کا ہولڈنگ فنکشن حاصل کرنا ہے ، جو براہ راست تعمیر اور FMZ پر ہوتا ہے جب ایک جنرل پروٹوکول پلگ ان ڈیٹا حاصل کرتا ہے جو تبادلے کے ہولڈنگ انٹرفیس پر واپس آتا ہے۔positionیہ ایک ہی اعداد و شمار کی ساخت ہے۔

مکمل AOFEX فیوچر جنرل پروٹوکول پلگ ان کی مثال:

Go زبان

/*
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build xxx.go
*/

package main

import (
    "bytes"
    "encoding/hex"
    "crypto/sha1"
    "encoding/json"
    "errors"
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "sort"
    "strconv"
    "strings"
    "time"
    // proxy
    "golang.org/x/net/proxy"
    "crypto/tls"
    "math/rand"
)

var isUsedProxy bool = false    
var ct string = ""              
var direction string = "buy"    
var marginLevel float64 = 10    
var currSymbol string = ""      


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
    }
    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
    }
    return ret
}

type headerTuple struct {
    name  string
    value string
}

type Request struct {
    headers     []headerTuple
    Proxy       string
    Method      string
    Uri         string
    Body        interface{}
    QueryString interface{}
    Timeout     time.Duration
    ContentType string
    Accept      string
    Host        string
    UserAgent   string
}

func (r *Request) AddHeader(name string, value string) {
    if r.headers == nil {
        r.headers = []headerTuple{}
    }
    r.headers = append(r.headers, headerTuple{name: name, value: value})
}

type iAOFEX struct {
    accessKey     string
    secretKey     string
    clientID      string 
    currency      string
    opCurrency    string
    baseCurrency  string
    quoteCurrency string 
    apiBase       string
    timeout       time.Duration
    timeLocation  *time.Location

    // ext
    contractTypes []string
}

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].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 newiAOFEX(accessKey, secretKey string) *iAOFEX {
    s := new(iAOFEX)
    s.accessKey = accessKey
    s.secretKey = secretKey
    s.apiBase = "https://openapi-contract.aofex.info"
    s.timeout = 20 * time.Second
    s.timeLocation = time.FixedZone("Asia/Shanghai", 8*60*60)
    s.contractTypes = []string{"swap"}
    return s
}

func (p *iAOFEX) isValidContractType(contractType string) bool {
    for _, t := range p.contractTypes {
        if contractType == t {
            return true
        }
    }
    return false
}

func (p *iAOFEX) calcContractTypeMap(currency string) (contractTypeMap map[string]string, err error) {
    var baseCurrency, quoteCurrency string 
    contractTypeMap = map[string]string{}
    if arr := strings.SplitN(currency, "_", 2); len(arr) == 2 {
        baseCurrency = arr[0]
        quoteCurrency = arr[1]
    } else {
        err = errors.New("symbol error!")
        return 
    }
    contractTypeMap["swap"] = fmt.Sprintf("%s-%s", strings.ToUpper(baseCurrency), strings.ToUpper(quoteCurrency))
    return
}

func (p *iAOFEX) apiCall(method string) (*Json, error) {
    req, err := http.NewRequest("GET", fmt.Sprintf("%s%s", p.apiBase, method), nil)
    if err != nil {
        return nil, err
    }

    fmt.Printf("\n %c[1;44;32m%s%c[0m\n", 0x1B, "apiCall GET create req:" + fmt.Sprintf("%s%s", p.apiBase, method), 0x1B)
    fmt.Println("req:", req)                                                        
    req.Header.Set("Content-Type", "application/json;utf-8")

    // proxy
    strProxy := ""
    client := http.DefaultClient
    if isUsedProxy {
        var auth *proxy.Auth
        proxyAddr := strings.Split(strProxy, "//")[1]
        if strings.Contains(proxyAddr, "@") {
            arr := strings.SplitN(proxyAddr, "@", 2)
            arrAuth := strings.SplitN(arr[0], ":", 2)
            proxyAddr = arr[1]
            auth = &proxy.Auth{}
            auth.User = arrAuth[0]
            if len(arrAuth) == 2 {
                auth.Password = arrAuth[1]
            }
        }
        var dialer proxy.Dialer
        if dialer, err = proxy.SOCKS5("tcp", proxyAddr, auth, proxy.Direct); err == nil {
            client = &http.Client{
                Transport: &http.Transport{
                    Dial:                  dialer.Dial,
                    MaxIdleConnsPerHost:   5,
                    TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
                    ResponseHeaderTimeout: 20 * time.Second,
                },
                Timeout: 20 * time.Second,
            }
        } else {
            return nil, err
        }
    }


    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    var js *Json
    js, err = NewJson(b)
    if err != nil {
        return nil, err
    }

    // fault tolerant 
    if _, ok := js.data.(map[string]interface{}); ok {
        if code, ok := js.MustMap()["code"]; ok {
            if toString(code) != "0" {
                err = errors.New(fmt.Sprintf("%v", js.data))
            }
        }
    }    

    return js, err 
}

func (p *iAOFEX) GetTicker(symbol string) (ticker interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    var js *Json
    js, err = p.apiCall(fmt.Sprintf("/openApi/contract/market?symbol=%s", realCt))
    if err != nil {
        return
    }

    depth, errDepth := p.GetDepth(symbol)
    if errDepth != nil {
        err = errDepth
        return 
    }

    ask1 := depth.(map[string]interface{})["asks"].([][2]float64)[0][0]
    bid1 := depth.(map[string]interface{})["bids"].([][2]float64)[0][0]
    mp := js.Get("result").MustMap()
    ticker = map[string]interface{}{
        "time": time.Now().UnixNano() / 1e6,
        "buy":  toFloat(bid1),
        "sell": toFloat(ask1),
        "last": toFloat(mp["close"]),
        "high": toFloat(mp["high"]),
        "low":  toFloat(mp["low"]),
        "vol":  toFloat(mp["vol"]),
    }
    return
}

func (p *iAOFEX) GetDepth(symbol string) (depth interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    var js *Json
    js, err = p.apiCall(fmt.Sprintf("/openApi/contract/depth?symbol=%s", realCt))
    if err != nil {
        return
    }

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

func (p *iAOFEX) GetTrades(symbol string) (trades interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    var js *Json
    js, err = p.apiCall(fmt.Sprintf("/openApi/contract/trade?symbol=%s", realCt))
    if err != nil {
        return
    }

    items := []map[string]interface{}{}
    for _, pair := range js.Get("result").Get("data").MustArray() {
        item := map[string]interface{}{}
        mp := pair.(map[string]interface{})
        item["id"] = toString(mp["id"])
        item["price"] = toFloat(mp["price"])
        item["amount"] = toFloat(mp["amount"])
        item["time"] = toInt64(mp["ts"])
        if toString(mp["direction"]) == "buy" {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }
        items = append(items, item)
    }
    
    for i := 0; i < len(items); i++ {
        for j := 0; j < len(items)-i-1; j++ {
            if toInt64(items[j]["time"]) > toInt64(items[j+1]["time"]) {
                items[j], items[j+1] = items[j+1], items[j]
            }
        }
    }

    trades = items
    return
}

func (p *iAOFEX) GetRecords(step int64, symbol string) (records interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }
    
    var periodDict map[int64]string = map[int64]string{
        1 : "1min",
        5 : "5min",
        15 : "15min",
        30 : "30min",
        60 : "1hour",
        1440 : "1day",
    }
    period, okPeriod := periodDict[step]
    if !okPeriod {
        err = errors.New("period not support")
        return 
    }

    var js *Json
    js, err = p.apiCall(fmt.Sprintf("/openApi/contract/kline?symbol=%s&period=%s&size=500", realCt, period))
    if err != nil {
        return
    }

    items := []interface{}{}
    recordsData := js.Get("result").Get("data").MustArray()
    for i := len(recordsData) - 1 ; i >= 0 ; i-- {
        mp := recordsData[i].(map[string]interface{})
        item := [6]interface{}{}
        item[0] = toInt64(mp["id"])          // time
        item[1] = toFloat(mp["open"])        // open
        item[2] = toFloat(mp["high"])        // high
        item[3] = toFloat(mp["low"])         // low
        item[4] = toFloat(mp["close"])       // close
        item[5] = toFloat(mp["vol"])         // vol
        items = append(items, item)
    }
    records = items
    return
}

func JSON_Encode(d interface{}) string {
    buffer := &bytes.Buffer{}
    encoder := json.NewEncoder(buffer)
    encoder.SetEscapeHTML(false)
    encoder.Encode(d)
    return buffer.String()
}

func (p *iAOFEX) tapiCall(httpMethod string, method string, params map[string]string) (js *Json, err error) {
    if params == nil {
        params = map[string]string{}
    }

    nonce := toString(time.Now().UnixNano() / 1e9)
    strLetter := "124567890abcdefghijklmnopqrstuvwxyz"
    for i := 0 ; i < 5 ; i++ {
        rand.Seed(time.Now().UnixNano())
        nonce += string(strLetter[rand.Intn(len(strLetter))])
    }

    arrParams := []string{}
    arrParams = append(arrParams, p.accessKey)
    arrParams = append(arrParams, p.secretKey)
    arrParams = append(arrParams, nonce)

    for k, v := range params {
        arrParams = append(arrParams, fmt.Sprintf("%s=%s", k, v))
    }

    sort.Strings(arrParams)
    strSign := ""
    for _, ele := range arrParams {
        strSign += toString(ele)
    }
    h := sha1.New()
    h.Write([]byte(strSign))
    signature := hex.EncodeToString(h.Sum(nil))

    strUrl := fmt.Sprintf("%s%s", p.apiBase, method)
    if len(params) > 0 {
        strUrl = fmt.Sprintf("%s%s?%s", p.apiBase, method, encodeParams(params, false))
    }

    req, err := http.NewRequest(httpMethod, strUrl, nil)
    if err != nil {
        return nil, err
    }
    req.Header.Add("Nonce", nonce)
    req.Header.Add("Token", p.accessKey)
    req.Header.Add("Signature", signature)
    
    strProxy := ""
    client := http.DefaultClient
    if isUsedProxy {
        var auth *proxy.Auth
        proxyAddr := strings.Split(strProxy, "//")[1]
        if strings.Contains(proxyAddr, "@") {
            arr := strings.SplitN(proxyAddr, "@", 2)
            arrAuth := strings.SplitN(arr[0], ":", 2)
            proxyAddr = arr[1]
            auth = &proxy.Auth{}
            auth.User = arrAuth[0]
            if len(arrAuth) == 2 {
                auth.Password = arrAuth[1]
            }
        }
        var dialer proxy.Dialer
        if dialer, err = proxy.SOCKS5("tcp", proxyAddr, auth, proxy.Direct); err == nil {
            client = &http.Client{
                Transport: &http.Transport{
                    Dial:                  dialer.Dial,
                    MaxIdleConnsPerHost:   5,
                    TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
                    ResponseHeaderTimeout: 20 * time.Second,
                },
                Timeout: 20 * time.Second,
            }
        } else {
            return nil, err
        }
    }

    fmt.Printf("\n %c[1;44;32m%s%c[0m\n", 0x1B, "apiCall GET create req:" + fmt.Sprintf("%s%s", p.apiBase, method), 0x1B)
    fmt.Println("tapiCall req:", req)  

    resp, err := client.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 err != nil {
        return nil, err
    }

    // fault tolerant 
    if mp, ok := js.data.(map[string]interface{}); ok {
        if errno, ok := mp["errno"]; !ok || toString(errno) != "0" {
            err = errors.New(fmt.Sprintf("%v", js.data))    
        }
    } else {
        err = errors.New(fmt.Sprintf("%v", js.data))
    }

    return js, err
}

func (p *iAOFEX) GetAccount(symbol string) (account interface{}, err error) { 
    symbol = currSymbol
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    var js *Json
    js, err = p.tapiCall("GET", "/openApi/contract/walletList", nil)
    if err != nil {
        return
    }

    assets := map[string]map[string]interface{}{}
    for _, ele := range js.Get("result").MustArray() {
        dic := ele.(map[string]interface{})
        if realCt != toString(dic["symbol"]) {
            continue
        }
        arr := strings.SplitN(toString(dic["symbol"]), "-", 2)
        if arr[1] == "USDT" {
            if _, ok := assets[arr[1]]; !ok {
                assets[arr[1]] = map[string]interface{}{}
            }
            assets[arr[1]]["currency"] = arr[1]
            assets[arr[1]]["Info"] = dic
            assets[arr[1]]["free"] = toFloat(dic["avail"])
            assets[arr[1]]["frozen"] = toFloat(dic["frozen"])
        }
    }

    accounts := []map[string]interface{}{}
    for _, pair := range assets {
        accounts = append(accounts, pair)
    }

    account = accounts
    return
}

func (p *iAOFEX) Trade(side string, price, amount float64, symbol string) (orderId interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    params := map[string]string{}
    
    if direction == "buy" {
        params["contract_type"] = "open"
    } else if direction == "sell" {
        params["contract_type"] = "open"
    } else if direction == "closebuy" {
        params["contract_type"] = "close" 
    } else if direction == "closesell" { 
        params["contract_type"] = "close"
    } else {
        err = errors.New("invalid direction!")
        return 
    }

    if (side == "buy-limit" || side == "buy-market") && (direction == "sell" || direction == "closebuy") {
        err = errors.New("invalid direction!")
        return 
    } else if (side == "sell-limit" || side == "sell-market") && (direction == "buy" || direction == "closesell") {
        err = errors.New("invalid direction!")
        return 
    }

    params["type"] = side
    params["lever_rate"] = toString(marginLevel)
    params["amount"] = toString(amount)
    params["symbol"] = realCt
    if price > 0 {
        params["price"] = toString(price)
    }

    var js *Json
    js, err = p.tapiCall("POST", "/openApi/contract/add", params)
    if err != nil {
        return
    }

    orderId = map[string]string{"id": toString(js.MustMap()["result"])}
    return
}

func (p *iAOFEX) GetOrders(symbol string) (orders interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    from := ""
    limit := 100
    items := []map[string]interface{}{}
    for {
        params := map[string]string{
            "symbol" : realCt, 
            "limit" : toString(limit),             
        }
        if from != "" {
            params["from"] = toString(from)
        }

        var js *Json
        js, err = p.tapiCall("GET", "/openApi/contract/currentList", params)
        if err != nil {
            return
        }

        arr := js.Get("result").MustArray()
        for _, ele := range arr {
            mp := ele.(map[string]interface{})
            item := map[string]interface{}{}
            item["id"] = toString(mp["order_id"])
            item["amount"] = toFloat(mp["amount"])
            item["price"] = toFloat(mp["price"])
            item["deal_amount"] = toFloat(mp["deal_amount"])
            item["avg_price"] = toFloat(mp["price_avg"])        
            if toString(mp["type"]) == "buy-limit" || toString(mp["type"]) == "buy-market" || toString(mp["type"]) == "buy-tactics" || toString(mp["type"]) == "buy-market-tactic" || toString(mp["type"]) == "buy-plan" || toString(mp["type"]) == "buy-market-plan" {
                item["type"] = "buy"
            } else {
                item["type"] = "sell"
            }
            item["status"] = "open"
            item["contract_type"] = ct
            items = append(items, item)
            from = toString(mp["order_id"])
        }

        if len(arr) < limit {
            break
        }
    }
    return items, nil
}

func (p *iAOFEX) GetOrder(orderId string, symbol string) (order interface{}, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    var js *Json
    js, err = p.tapiCall("GET", "/openApi/contract/historyList", map[string]string{
        "from" : toString(orderId),
        "symbol" : realCt, 
        "limit" : "1",
    })
    if err != nil {
        return
    }

    item := map[string]interface{}{}
    for _, ele := range js.Get("result").MustArray() {
        mp := ele.(map[string]interface{})
        if realCt != toString(mp["symbol"]) || toString(mp["order_id"]) != toString(orderId) {
            continue
        }
        item["id"] = toString(mp["order_id"])
        item["amount"] = toFloat(mp["amount"])
        item["price"] = toFloat(mp["price"])
        item["deal_amount"] = toFloat(mp["deal_amount"])
        item["avg_price"] = toFloat(mp["price_avg"])
        item["contract_type"] = ct
        if toString(mp["type"]) == "buy-limit" || toString(mp["type"]) == "buy-market" || toString(mp["type"]) == "buy-tactics" || toString(mp["type"]) == "buy-market-tactic" || toString(mp["type"]) == "buy-plan" || toString(mp["type"]) == "buy-market-plan" {
            item["type"] = "buy"
        } else {
            item["type"] = "sell"
        }    

        switch toString(mp["status"]) {
        case "1", "2":
            item["status"] = "open"
        case "3":
            item["status"] = "closed"
        case "4", "5", "6":
            item["status"] = "cancelled"
        }
        return item, nil
    }

    err = errors.New("order not found")
    return 
}

func (p *iAOFEX) CancelOrder(orderId string, symbol string) (ret bool, err error) {
    mpCt, mpCtErr := p.calcContractTypeMap(symbol)
    if mpCtErr != nil {
        err = mpCtErr
        return 
    }
    realCt, ok := mpCt[ct]
    if !ok {
        err = errors.New("invalid contractType!")
        return 
    }

    _, err = p.tapiCall("POST", "/openApi/contract/cancel", map[string]string{
        "order_ids" : toString(orderId), 
        "symbol" : realCt, 
    })
    if err != nil {
        return
    }

    ret = true
    return
}

type RpcRequest struct {                                        
    AccessKey string            `json:"access_key"`
    SecretKey string            `json:"secret_key"`
    Nonce     int64             `json:"nonce"`
    Method    string            `json:"method"`
    Params    map[string]interface{} `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 := newiAOFEX(request.AccessKey, request.SecretKey)
    symbol := strings.ToUpper(toString(request.Params["symbol"]))
    if _, ok := request.Params["symbol"]; ok {        
        currSymbol = symbol        
    }

    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 := toString(request.Params["type"])
        if side == "buy" {
            side = "buy-limit"
            if toFloat(request.Params["price"]) <= 0 {
                side = "buy-market"
            }
        } else {
            side = "sell-limit"
            if toFloat(request.Params["price"]) <= 0 {
                side = "sell-market"
            }
        }
        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(toString(request.Params["id"]), symbol)
    case "cancel":
        data, err = e.CancelOrder(toString(request.Params["id"]), symbol)
    default:
        if strings.HasPrefix(request.Method, "__api_") {  
            params := map[string]string{}
            for k, v := range request.Params {
                params[k] = toString(v)
            }
            data, err = e.tapiCall("GET", request.Method[6:], params)
        } else if request.Method == "io" {
            code := toString(request.Params["code"])
            if code == "0" {            
                if args, ok := request.Params["args"].([]interface{}); ok && len(args) == 1 {
                    marginLevel = toFloat(args[0])
                } else {
                    err = errors.New(fmt.Sprintf("%v", request.Params))
                }
            } else if code == "1" {
                if args, ok := request.Params["args"].([]interface{}); ok && len(args) == 1 {
                    orderDirection := toString(args[0])
                    if orderDirection == "buy" || orderDirection == "sell" || orderDirection == "closebuy" || orderDirection == "closesell" {
                        direction = orderDirection
                        data = orderDirection
                    } else {
                        err = errors.New(fmt.Sprintf("not support orderDirection: %s", orderDirection))
                    }
                } else {
                    err = errors.New(fmt.Sprintf("%v", request.Params))
                }
            } else if code == "2" {     
                if args, ok := request.Params["args"].([]interface{}); ok && len(args) == 1 {
                    contractType := toString(args[0])
                    if e.isValidContractType(contractType) {
                        ct = contractType
                        data = contractType
                    } else {
                        err = errors.New(fmt.Sprintf("not support contractType: %s", contractType))
                    }
                } else {
                    err = errors.New(fmt.Sprintf("%v", request.Params))
                }
            } else if code == "3" {     
                symbol := currSymbol
                mpCt, mpCtErr := e.calcContractTypeMap(symbol)
                if mpCtErr != nil {
                    panic(mpCtErr)
                }
                realCt, ok := mpCt[ct]
                if !ok {
                    err = errors.New("invalid contractType!")
                    panic(err)
                }
                var js *Json
                js, err = e.tapiCall("GET", "/openApi/contract/position", map[string]string{
                    "symbol" : realCt, 
                })
                if err != nil {
                    panic(err)
                }

                items := []map[string]interface{}{}
                for _, ele := range js.Get("result").MustArray() {
                    mp := ele.(map[string]interface{})
                    item := map[string]interface{}{}
                    item["MarginLevel"] = toFloat(mp["lever_rate"])
                    item["Amount"] = toFloat(mp["amount"])
                    item["FrozenAmount"] = toFloat(mp["contract_frozen"])
                    item["Price"] = toFloat(mp["open_price_avg"])
                    item["Profit"] = toFloat(mp["un_profit"])
                    if toString(mp["type"]) == "1" {
                        item["Type"] = 0
                    } else {
                        item["Type"] = 1
                    }
                    item["ContractType"] = ct
                    item["Margin"] = toFloat(mp["bood"])
                    items = append(items, item)
                }
                data = items
            } else {
                panic(errors.New(request.Method + " not support"))    
            }
        } 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:6617", "bind addr")   
    flag.Parse()
    if *addr == "" {
        flag.Usage()
        return
    }
    basePath := "/AOFEX"
    log.Println("Running ", fmt.Sprintf("http://%s%s", *addr, basePath), "...")
    http.HandleFunc(basePath, OnPost)
    http.ListenAndServe(*addr, nil)
}

متعلقہ

مزید

ٹریک حیاتیاتیہ بہت مشکل ہے.

ایجاد کاروں کی مقدار - خوابایک پروگرامر کو ڈھونڈیں اور دو دن میں آپ کی مدد کریں۔