Type/to search
8
Follow
1361
Followers
لیک ہارویسٹر کی حکمت عملی کا تجزیہ (1)
HFT
Created 2020-11-12 22:11:32  Updated 2024-12-06 22:20:54
 26
 11224

img

لیک ہارویسٹر کی حکمت عملی کا تجزیہ

حال ہی میں، مقداری WeChat گروپ ڈسکشن کے موجدprint moneyروبوٹس کی بحث بہت گرم تھی، اور ایک بہت پرانی حکمت عملی مقدار کے وژن میں دوبارہ داخل ہوئی:لیک کاٹنے والا
print moneyروبوٹ کا تجارتی اصول لیک ہارویسٹر کی حکمت عملی پر مبنی ہے، میں اس وقت لیک ہارویسٹر کی حکمت عملی کو واضح طور پر نہ سمجھنے کا الزام لگاتا ہوں۔ لہذا، میں نے دوبارہ اصل حکمت عملی کا بغور جائزہ لیا، اور انوینٹر کوانٹ پر ٹرانسپلانٹ شدہ ورژن کا بھی جائزہ لیا۔ٹرانسپلانٹ OKCoin Leek ہارویسٹر
آئیے حکمت عملی کا تجزیہ کرنے اور اس کے پیچھے خیالات کو دریافت کرنے کے لیے موجد مقداری پلیٹ فارم کی لیک ہارویسٹر حکمت عملی کا ٹرانسپلانٹڈ ورژن لیں۔ تاکہ پلیٹ فارم استعمال کرنے والے اس اسٹریٹجک آئیڈیا کو سیکھ سکیں۔
اس مضمون میں، ہم اسٹریٹجک سوچ، ارادے وغیرہ کے نقطہ نظر سے مزید تجزیہ کریں گے، اور پروگرامنگ سے متعلق بورنگ مواد کو کم کرنے کی کوشش کریں گے۔

[ٹرانسپلانٹ OKCoin Leek Harvester] اسٹریٹیجی سورس کوڈ:

pine
function LeeksReaper() { var self = {} self.numTick = 0 self.lastTradeId = 0 self.vol = 0 self.askPrice = 0 self.bidPrice = 0 self.orderBook = {Asks:[], Bids:[]} self.prices = [] self.tradeOrderId = 0 self.p = 0.5 self.account = null self.preCalc = 0 self.preNet = 0 self.updateTrades = function() { var trades = _C(exchange.GetTrades) if (self.prices.length == 0) { while (trades.length == 0) { trades = trades.concat(_C(exchange.GetTrades)) } for (var i = 0; i < 15; i++) { self.prices[i] = trades[trades.length - 1].Price } } self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) { // Huobi not support trade.Id if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) { self.lastTradeId = Math.max(trade.Id == 0 ? trade.Time : trade.Id, self.lastTradeId) mem += trade.Amount } return mem }, 0) } self.updateOrderBook = function() { var orderBook = _C(exchange.GetDepth) self.orderBook = orderBook if (orderBook.Bids.length < 3 || orderBook.Asks.length < 3) { return } self.bidPrice = orderBook.Bids[0].Price * 0.618 + orderBook.Asks[0].Price * 0.382 + 0.01 self.askPrice = orderBook.Bids[0].Price * 0.382 + orderBook.Asks[0].Price * 0.618 - 0.01 self.prices.shift() self.prices.push(_N((orderBook.Bids[0].Price + orderBook.Asks[0].Price) * 0.35 + (orderBook.Bids[1].Price + orderBook.Asks[1].Price) * 0.1 + (orderBook.Bids[2].Price + orderBook.Asks[2].Price) * 0.05)) } self.balanceAccount = function() { var account = exchange.GetAccount() if (!account) { return } self.account = account var now = new Date().getTime() if (self.orderBook.Bids.length > 0 && now - self.preCalc > (CalcNetInterval * 1000)) { self.preCalc = now var net = _N(account.Balance + account.FrozenBalance + self.orderBook.Bids[0].Price * (account.Stocks + account.FrozenStocks)) if (net != self.preNet) { self.preNet = net LogProfit(net) } } self.btc = account.Stocks self.cny = account.Balance self.p = self.btc * self.prices[self.prices.length-1] / (self.btc * self.prices[self.prices.length-1] + self.cny) var balanced = false if (self.p < 0.48) { Log("开始平衡", self.p) self.cny -= 300 if (self.orderBook.Bids.length >0) { exchange.Buy(self.orderBook.Bids[0].Price + 0.00, 0.01) exchange.Buy(self.orderBook.Bids[0].Price + 0.01, 0.01) exchange.Buy(self.orderBook.Bids[0].Price + 0.02, 0.01) } } else if (self.p > 0.52) { Log("开始平衡", self.p) self.btc -= 0.03 if (self.orderBook.Asks.length >0) { exchange.Sell(self.orderBook.Asks[0].Price - 0.00, 0.01) exchange.Sell(self.orderBook.Asks[0].Price - 0.01, 0.01) exchange.Sell(self.orderBook.Asks[0].Price - 0.02, 0.01) } } Sleep(BalanceTimeout) var orders = exchange.GetOrders() if (orders) { for (var i = 0; i < orders.length; i++) { if (orders[i].Id != self.tradeOrderId) { exchange.CancelOrder(orders[i].Id) } } } } self.poll = function() { self.numTick++ self.updateTrades() self.updateOrderBook() self.balanceAccount() var burstPrice = self.prices[self.prices.length-1] * BurstThresholdPct var bull = false var bear = false var tradeAmount = 0 if (self.account) { LogStatus(self.account, 'Tick:', self.numTick, ', lastPrice:', self.prices[self.prices.length-1], ', burstPrice: ', burstPrice) } if (self.numTick > 2 && ( self.prices[self.prices.length-1] - _.max(self.prices.slice(-6, -1)) > burstPrice || self.prices[self.prices.length-1] - _.max(self.prices.slice(-6, -2)) > burstPrice && self.prices[self.prices.length-1] > self.prices[self.prices.length-2] )) { bull = true tradeAmount = self.cny / self.bidPrice * 0.99 } else if (self.numTick > 2 && ( self.prices[self.prices.length-1] - _.min(self.prices.slice(-6, -1)) < -burstPrice || self.prices[self.prices.length-1] - _.min(self.prices.slice(-6, -2)) < -burstPrice && self.prices[self.prices.length-1] < self.prices[self.prices.length-2] )) { bear = true tradeAmount = self.btc } if (self.vol < BurstThresholdVol) { tradeAmount *= self.vol / BurstThresholdVol } if (self.numTick < 5) { tradeAmount *= 0.8 } if (self.numTick < 10) { tradeAmount *= 0.8 } if ((!bull && !bear) || tradeAmount < MinStock) { return } var tradePrice = bull ? self.bidPrice : self.askPrice while (tradeAmount >= MinStock) { var orderId = bull ? exchange.Buy(self.bidPrice, tradeAmount) : exchange.Sell(self.askPrice, tradeAmount) Sleep(200) if (orderId) { self.tradeOrderId = orderId var order = null while (true) { order = exchange.GetOrder(orderId) if (order) { if (order.Status == ORDER_STATE_PENDING) { exchange.CancelOrder(orderId) Sleep(200) } else { break } } } self.tradeOrderId = 0 tradeAmount -= order.DealAmount tradeAmount *= 0.9 if (order.Status == ORDER_STATE_CANCELED) { self.updateOrderBook() while (bull && self.bidPrice - tradePrice > 0.1) { tradeAmount *= 0.99 tradePrice += 0.1 } while (bear && self.askPrice - tradePrice < -0.1) { tradeAmount *= 0.99 tradePrice -= 0.1 } } } } self.numTick = 0 } return self } function main() { var reaper = LeeksReaper() while (true) { reaper.poll() Sleep(TickInterval) } }

حکمت عملی کا جائزہ

عام طور پر، جب آپ کو سیکھنے کی حکمت عملی ملتی ہے، جب آپ اسے پڑھتے ہیں، تو آپ کو پہلے پروگرام کے مجموعی ڈھانچے کو دیکھنا چاہیے۔ اس حکمت عملی میں زیادہ کوڈ نہیں ہے، کوڈ کی صرف 200 لائنوں سے کم ہے، جو بہت مختصر ہے، اور اصل حکمت عملی کی بحالی کی ڈگری بہت زیادہ ہے، اور یہ بنیادی طور پر ایک ہی ہے۔ پالیسی کوڈ سے چلتا ہے۔main()فنکشن پر عمل درآمد شروع ہوتا ہے، اور پوری حکمت عملی کوڈ ہے۔main(), ایک ہےLeeksReaper()فنکشن،LeeksReaper()فنکشن کو سمجھنا بھی آسان ہے اس فنکشن کو لیک ہارویسٹر لائجک ماڈیول (سادہ الفاظ میں) کے کنسٹرکٹر کے طور پر سمجھا جا سکتا ہے۔LeeksReaper()یہ لیک ہارویسٹر ٹرانزیکشن منطق کی تعمیر کے لیے ذمہ دار ہے۔

مطلوبہ الفاظ:
img
img

  • حکمت عملیmainفنکشن کی پہلی لائن:
    var reaper = LeeksReaper()، کوڈ ایک مقامی متغیر کا اعلان کرتا ہے۔reaperپھر ایک حکمت عملی منطق آبجیکٹ بنانے کے لیے LeeksReaper() فنکشن کو کال کریں اور اسے تفویض کریں۔reaper

  • حکمت عملیmainفنکشن اگلا ہے:

    javascript
    while (true) { reaper.poll() Sleep(TickInterval) }

    درج کریں awhileمردہ لوپ، لامتناہی پھانسیreaperآبجیکٹ پروسیسنگ فنکشنpoll()poll()فنکشن ٹریڈنگ کی حکمت عملی کی بنیادی منطق ہے، اور پورے حکمت عملی کا پروگرام مسلسل تجارتی منطق کو انجام دینا شروع کر دیتا ہے۔
    کے طور پرSleep(TickInterval)اس لائن کو سمجھنا آسان ہے کہ لین دین کی منطق کی گردش کی فریکوئنسی کو کنٹرول کرنے کے لیے، مجموعی لین دین کی منطق کے ہر عمل کے بعد وقفے کے وقت کو کنٹرول کرنا ہے۔

پروفائلنگLeeksReaper()کنسٹرکٹر

ایک نظرLeeksReaper()فنکشنز حکمت عملی منطق آبجیکٹ کو کس طرح تشکیل دیتے ہیں۔

LeeksReaper()فنکشن کے آغاز میں، ایک خالی چیز کا اعلان کیا جاتا ہے.var self = {},موجودLeeksReaper()فنکشن کی تکمیل کے دوران، کچھ طریقے اور خواص آہستہ آہستہ اس خالی چیز میں شامل کیے جائیں گے، اور آخر کار اس آبجیکٹ کی تعمیر مکمل ہو جائے گی، اور آخر میں یہ شے واپس کر دی جائے گی (یعنی،main()فنکشن کے اندرvar reaper = LeeksReaper()اس مرحلے میں، واپس کردہ آبجیکٹ کو تفویض کیا گیا ہے۔reaper)。

دیناselfکسی چیز میں خصوصیات شامل کرنا

اگلا دیناselfبہت ساری خصوصیات شامل کی گئی ہیں میں ذیل میں ہر ایک پراپرٹی کو بیان کروں گا تاکہ آپ ان خصوصیات اور متغیرات کے مقصد اور ارادے کو جلدی سے سمجھ سکیں اور حکمت عملی کو آسانی سے سمجھ سکیں، تاکہ کوڈ کے اس ڈھیر کو دیکھ کر آپ الجھن سے بچ سکیں۔

python
self.numTick = 0 # 用来记录poll函数调用时未触发交易的次数,当触发下单并且下单逻辑执行完时,self.numTick重置为0 self.lastTradeId = 0 # 交易市场已经成交的订单交易记录ID,这个变量记录市场当前最新的成交记录ID self.vol = 0 # 通过加权平均计算之后的市场每次考察时成交量参考(每次循环获取一次市场行情数据,可以理解为考察了行情一次) self.askPrice = 0 # 卖单提单价格,可以理解为策略通过计算后将要挂卖单的价格 self.bidPrice = 0 # 买单提单价格 self.orderBook = {Asks:[], Bids:[]} # 记录当前获取的订单薄数据,即深度数据(卖一...卖n,买一...买n) self.prices = [] # 一个数组,记录订单薄中前三档加权平均计算之后的时间序列上的价格,简单说就是每次储存计算得到的订单薄前三档加权平均价格,放在一个数组中,用于后续策略交易信号参考,所以该变量名是prices,复数形式,表示一组价格 self.tradeOrderId = 0 # 记录当前提单下单后的订单ID self.p = 0.5 # 仓位比重,币的价值正好占总资产价值的一半时,该值为0.5,即平衡状态 self.account = null # 记录账户资产数据,由GetAccount()函数返回数据 self.preCalc = 0 # 记录最近一次计算收益时的时间戳,单位毫秒,用于控制收益计算部分代码触发执行的频率 self.preNet = 0 # 记录当前收益数值

دیناselfآبجیکٹ شامل کرنے کا طریقہ

ان صفات کو اپنی ذات میں شامل کرنے کے بعد شروع کریں۔selfکسی چیز میں طریقے شامل کرنے سے آبجیکٹ کو کچھ کام کرنے اور کچھ کام کرنے کی اجازت ملتی ہے۔

پہلا فنکشن شامل کیا گیا:

cpp
self.updateTrades = function() { var trades = _C(exchange.GetTrades) # 调用FMZ封装的接口GetTrades,获取当前最新的市场成交数据 if (self.prices.length == 0) { # 当self.prices.length == 0时,需要给self.prices数组填充数值,只有策略启动运行时才会触发 while (trades.length == 0) { # 如果近期市场上没有更新的成交记录,这个while循环会一直执行,直到有最新成交数据,更新trades变量 trades = trades.concat(_C(exchange.GetTrades)) # concat 是JS数组类型的一个方法,用来拼接两个数组,这里就是把“trades”数组和“_C(exchange.GetTrades)”返回的数组数据拼接成一个数组 } for (var i = 0; i < 15; i++) { # 给self.prices填充数据,填充15个最新成交价格 self.prices[i] = trades[trades.length - 1].Price } } self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) { # _.reduce 函数迭代计算,累计最新成交记录的成交量 // Huobi not support trade.Id if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) { self.lastTradeId = Math.max(trade.Id == 0 ? trade.Time : trade.Id, self.lastTradeId) mem += trade.Amount } return mem }, 0) }

updateTradesاس فنکشن کا مقصد مارکیٹ کے لین دین کا تازہ ترین ڈیٹا حاصل کرنا، ڈیٹا کی بنیاد پر کچھ حسابات کرنا اور حکمت عملی کے بعد کی منطق میں استعمال کے لیے انہیں ریکارڈ کرنا ہے۔
میں نے لائن بہ لائن تبصرے براہ راست اوپر والے کوڈ میں لکھے۔
کے لیے_.reduceجن طلباء کے پاس پروگرامنگ فاؤنڈیشن نہیں ہے وہ الجھن میں پڑ سکتے ہیں یہاں ایک مختصر وضاحت ہے۔_.reduceہاںUnderscore.jsاس لائبریری کے افعال کو FMZJS حکمت عملی سے تعاون حاصل ہے، اس لیے تکراری حسابات کا استعمال کرنا بہت آسان ہے۔Underscore.js ڈیٹا لنک

معنی بھی بہت آسان ہے، مثلاً:

python
function main () { var arr = [1, 2, 3, 4] var sum = _.reduce(arr, function(ret, ele){ ret += ele return ret }, 0) Log("sum:", sum) # sum 等于 10 }

یعنی صف[1, 2, 3, 4]میں ہر ایک نمبر کو شامل کریں۔ ہماری حکمت عملی پر واپس،tradesصف میں ہر لین دین کے ریکارڈ کی ٹرانزیکشن والیوم ویلیوز کو شامل کیا جاتا ہے۔ تازہ ترین لین دین کا ریکارڈ اور کل لین دین کا حجم حاصل کریں۔self.vol = 0.7 * self.vol + 0.3 * _.reduce(...)براہ کرم مجھے استعمال کرنے کی اجازت دیں۔...کوڈ کے اس گروپ کے بجائے۔ یہاں یہ دیکھنا مشکل نہیں ہے۔self.volکا حساب بھی ایک وزنی اوسط ہے۔ یعنی، تازہ ترین کل لین دین کا حجم وزن کا 30% ہے، اور پچھلے وزنی حساب سے حاصل کردہ لین دین کا حجم 70% ہے۔ یہ تناسب حکمت عملی کے مصنف کے ذریعہ مصنوعی طور پر ترتیب دیا گیا ہے اور اس کا تعلق مارکیٹ کے نمونوں کو دیکھنے سے ہوسکتا ہے۔
جہاں تک آپ کے سوال کا تعلق ہے، اگر تازہ ترین ٹرانزیکشن ڈیٹا حاصل کرنے کا انٹرفیس مجھے پرانا ڈیٹا واپس کرتا ہے تو جو ڈیٹا مجھے ملتا ہے وہ غلط ہوگا، تو کیا اسے استعمال کرنے کا کوئی فائدہ ہے؟ پریشان نہ ہوں، حکمت عملی تیار کرتے وقت اس مسئلے کو مدنظر رکھا گیا تھا، اس لیے اسے کوڈ میں شامل کیا گیا ہے۔

python
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) { ... }

یہ فیصلہ۔ اس کا اندازہ ٹرانزیکشن ریکارڈ میں موجود ٹرانزیکشن ID کی بنیاد پر کیا جا سکتا ہے جب کہ ID آخری ریکارڈ کی ID سے زیادہ ہو، یا اگر ایکسچینج انٹرفیس ID فراہم نہ کرے، یعنی،trade.Id == 0اس وقت تعین کرنے کے لیے لین دین کے ریکارڈ میں ٹائم اسٹیمپ کا استعمال کرتے ہوئےself.lastTradeIdجو ذخیرہ کیا جاتا ہے وہ ٹرانزیکشن ریکارڈ کا ٹائم اسٹیمپ ہوتا ہے، ID کا نہیں۔

دوسرا فنکشن شامل کیا گیا:

python
self.updateOrderBook = function() { var orderBook = _C(exchange.GetDepth) self.orderBook = orderBook if (orderBook.Bids.length < 3 || orderBook.Asks.length < 3) { return } self.bidPrice = orderBook.Bids[0].Price * 0.618 + orderBook.Asks[0].Price * 0.382 + 0.01 self.askPrice = orderBook.Bids[0].Price * 0.382 + orderBook.Asks[0].Price * 0.618 - 0.01 self.prices.shift() self.prices.push(_N((orderBook.Bids[0].Price + orderBook.Asks[0].Price) * 0.35 + (orderBook.Bids[1].Price + orderBook.Asks[1].Price) * 0.1 + (orderBook.Bids[2].Price + orderBook.Asks[2].Price) * 0.05)) }

اگلی گھڑیupdateOrderBookجیسا کہ نام سے پتہ چلتا ہے، یہ فنکشن آرڈر بک کو اپ ڈیٹ کرنے کے لیے استعمال کیا جاتا ہے۔ تاہم، یہ صرف آرڈر بک کو اپ ڈیٹ کرنے سے زیادہ ہے۔ فنکشن FMZ کے API فنکشن کو کال کرنا شروع کرتا ہے۔GetDepth()موجودہ مارکیٹ آرڈر بک کا ڈیٹا حاصل کریں (ایک بیچیں...بیچیں، خریدیں...بائین کریں)، اور آرڈر بک کا ڈیٹا ریکارڈ کریںself.orderBookدرمیانی اگلا، اگر آرڈر بک کے ڈیٹا میں 3 سے کم خرید و فروخت کے آرڈرز ہیں، تو فنکشن کو غلط سمجھا جاتا ہے اور براہ راست واپس آتا ہے۔

اس کے بعد، دو اعداد و شمار کا حساب لگایا گیا:

  • لڈنگ قیمت کے بل کا حساب لگائیں۔
    خریدی آرڈر کا حساب لگاتے ہوئے قیمت کے بل کا بھی حساب لگایا جاتا ہے، خرید آرڈر کو 61.8% (0.618) کا بڑا وزن دیا جاتا ہے، اور فروخت آرڈر کا باقی وزن 38.2% (0.382) ہوتا ہے۔
    فروخت کی قیمت کے بل کا حساب لگاتے وقت بھی یہی لاگو ہوتا ہے، جس میں فروخت کی قیمت کو زیادہ وزن دیا جاتا ہے۔ جہاں تک یہ 0.618 کیوں ہے، یہ ہو سکتا ہے کہ مصنف سنہری تناسب کو ترجیح دیتا ہو۔ جہاں تک قیمت میں حتمی معمولی اضافہ یا کمی (0.01) کا تعلق ہے، اسے تھوڑا سا مارکیٹ کے مرکز کی طرف منتقل کرنا ہے۔

  • ٹائم سیریز پر آرڈر بک کے پہلے تین درجوں کی وزنی اوسط قیمت کو اپ ڈیٹ کریں۔
    آرڈر بک میں خرید و فروخت کے آرڈر کی قیمتوں کے پہلے تین درجوں پر ایک وزنی اوسط کا حساب لگایا جاتا ہے، پہلے درجے کا وزن 0.7، دوسرے درجے کا وزن 0.2، اور تیسرے درجے کا وزن 0.1 ہوتا ہے۔ . کچھ طلباء کہہ سکتے ہیں: "اوہ، یہ ٹھیک نہیں ہے، کوڈ میں کوئی 0.7، 0.2، 0.1 نہیں ہے"
    آئیے حساب کو بڑھاتے ہیں:

    (买一 + 卖一) * 0.35 + (买二 + 卖二) * 0.1 + (买三 + 卖三) * 0.05 -> (买一 + 卖一) / 2 * 2 * 0.35 + (买二 + 卖二) / 2 * 2 * 0.1 + (买三 + 卖三) / 2 * 2 * 0.05 -> (买一 + 卖一) / 2 * 0.7 + (买二 + 卖二) / 2 * 0.2 + (买三 + 卖三) / 2 * 0.1 -> 第一档平均的价格 * 0.7 + 第二档平均的价格 * 0.2 + 第三档平均的价格 * 0.1

    یہاں آپ دیکھ سکتے ہیں کہ حتمی حسابی قیمت دراصل موجودہ مارکیٹ میں تین درجوں کی درمیانی قیمت کی پوزیشن کو ظاہر کرتی ہے۔
    پھر اس حسابی قیمت کو اپ ڈیٹ کرنے کے لیے استعمال کریں۔self.pricesسرنی، سب سے پرانے ڈیٹا (کے ذریعےshift()فنکشن)، تازہ ترین ڈیٹا کو اپ ڈیٹ کریں (کے ذریعےpush()فنکشن، شفٹ اور پش فنکشنز جے ایس لینگویج اری آبجیکٹ کے طریقے ہیں آپ تفصیلات کے لیے جے ایس کی معلومات چیک کر سکتے ہیں)۔ اس طرح تشکیلself.pricesایک صف ایک ٹائم سیریز آرڈر شدہ ڈیٹا اسٹریم ہے۔

احمد، تھوڑا پانی پیو، میں ابھی یہیں رکتا ہوں، اگلی بار ملتے ہیں۔

Related Recommendations
Comment
All comments (21)

    梦总你好,想问一下。self.prices 先填充的15个历史成交价格,然后又填充订单薄前三档加权平均价格. 对吧

    5 years ago

    是的。

    5 years ago

    我想给梦总点个赞

    5 years ago

    可惜了,很多交易所的做市商已经将盘口买一卖一价压到了只剩下一个tick,让策略里这种尝试插入到买一卖一中间的操作没有了意义

    6 years ago

    主要是学习思路,洞察这些可以高频的交易机会。

    6 years ago

    谢谢了,我写了python的版本在山寨币上跑了一下,真是手续费收割机啊。。。不到5分钟20刀没了:)

    6 years ago

    不客气,可以看下草神写的策略原理分析的文章,高频策略是需要一些支持的。

    6 years ago

    太棒了,没有梦总的解释还真的不能完全看懂,感谢大佬的耐心解释!

    6 years ago

    感谢支持~如果喜欢,帮忙分享下,嘿嘿~

    6 years ago

    黄金分割0.618 0.382是用的斐波
    梦总牛批

    6 years ago

    上学那会儿,学到这个黄金分割比例记得特别清楚,说是这样比例长宽的矩形最美~~但是不知道为什么。。。。

    6 years ago

    确实牛批啊这么详细

    6 years ago

    感谢支持!

    6 years ago

    大大的赞,这么详细的解答.感谢分享

    6 years ago

    感谢支持!

    6 years ago

    梦总牛p

    6 years ago

    感谢支持!

    6 years ago

    梦总,牛批!!
    虽然注释了,但是看起来还是非常复杂.....

    6 years ago

    其实不复杂,这次注释的比较啰嗦,逐行都尽量用最容易理解的方式描述了。通篇看下来应该理解不难。

    6 years ago

    梦总,牛批!!

    6 years ago

    感谢支持。

    6 years ago
  • 1
iPhone Download
Forums
PINE Language
© 2015 - ∞ INVENTOR PTE LTD (SG)