
Mục đích của bài viết này là chia sẻ một số kinh nghiệm và mẹo trong việc phát triển chiến lược, có thể giúp người đọc nhanh chóng hiểu được kinh nghiệm trong việc phát triển chiến lược giao dịch. Khi gặp phải những vấn đề chi tiết tương tự trong thiết kế chiến lược, các giải pháp hợp lý có thể được hình thành ngay lập tức. Nền tảng giao dịch định lượng Inventor được sử dụng làm nền tảng để giải thích, thử nghiệm và thực hành. Ngôn ngữ lập trình chính sách: JavaScript Thị trường giao dịch: thị trường tài sản blockchain (BTC, ETH, v.v.)
Thông thường, tùy thuộc vào logic chiến lược, có thể sử dụng các giao diện khác nhau sau đây để lấy dữ liệu thị trường, vì logic giao dịch của chiến lược thường được thúc đẩy bởi dữ liệu thị trường (tất nhiên, có một số chiến lược không xem xét thị trường (chẳng hạn như các chiến lược đầu tư cố định).
GetTicker: Nhận thông tin tích tắc theo thời gian thực. Nó thường được sử dụng để nhanh chóng có được giá mới nhất hiện tại, giá mua và giá bán.
GetDepth: Nhận báo giá theo chiều sâu sổ lệnh. Nó thường được sử dụng để lấy giá của từng cấp độ và quy mô của đơn hàng. Được sử dụng cho các chiến lược phòng ngừa rủi ro, chiến lược tạo lập thị trường, v.v.
GetTrade: Nhận hồ sơ giao dịch mới nhất trên thị trường. Phương pháp này thường được sử dụng để phân tích hành vi thị trường trong thời gian ngắn và phân tích những thay đổi nhỏ của thị trường. Thường được sử dụng trong các chiến lược tần suất cao và chiến lược thuật toán.
GetRecords: Lấy dữ liệu K-line của thị trường. Thường được sử dụng trong các chiến lược theo xu hướng. Được sử dụng để tính toán các chỉ số.
Khi thiết kế chiến lược, người mới thường bỏ qua nhiều tình huống lỗi khác nhau và trực giác tin rằng kết quả của từng liên kết trong chiến lược đã được xác định trước. Nhưng thực tế thì không phải vậy. Khi yêu cầu dữ liệu thị trường trong quá trình vận hành chương trình chiến lược, sẽ gặp phải nhiều tình huống bất ngờ. Ví dụ, một số giao diện thị trường trả về dữ liệu bất thường:
var depth = exchange.GetDepth()
// depth.Asks[0].Price < depth.Bids[0].Price 卖一价格低于了买一价格,这种情况不可能存在于盘面上,
// 因为卖出的价格低于买入的价格,必定已经成交了。
// depth.Bids[n].Amount = 0 订单薄买入列表第n档,订单量为0
// depth.Asks[m].Price = 0 订单薄卖出列表第m档,订单价格为0
Hoặc exchange.GetDepth() trực tiếp trả về giá trị null.
Có rất nhiều tình huống kỳ lạ như vậy. Do đó, cần phải thực hiện xử lý tương ứng cho những vấn đề có thể lường trước này và loại giải pháp xử lý này được gọi là xử lý chịu lỗi.
Phương pháp chịu lỗi thông thường là loại bỏ dữ liệu và truy xuất lại.
Ví dụ:
function main () {
while (true) {
onTick()
Sleep(500)
}
}
function GetTicker () {
while (true) {
var ticker = exchange.GetTicker()
if (ticker.Sell > ticker.Buy) { // 以 检测卖一价格是不是小于买一价这个错误的容错处理为例,
// 排除这个错误,当前函数返回 ticker 。
return ticker
}
Sleep(500)
}
}
function onTick () {
var ticker = GetTicker() // 确保获取到的 ticker 不会存在 卖一价格小于买一价格这种数据错误的情况。
// ... 具体的策略逻辑
}
Các quy trình chịu lỗi khác có thể được xử lý theo cách tương tự. Nguyên tắc thiết kế là không bao giờ được sử dụng dữ liệu không chính xác để thúc đẩy logic chiến lược.
Thu thập dữ liệu K-line, gọi:
var r = exchange.GetRecords()
Dữ liệu dòng K thu được là một mảng, như sau:
[
{"Time":1562068800000,"Open":10000.7,"High":10208.9,"Low":9942.4,"Close":10058.8,"Volume":6281.887000000001},
{"Time":1562072400000,"Open":10058.6,"High":10154.4,"Low":9914.5,"Close":9990.7,"Volume":4322.099},
...
{"Time":1562079600000,"Open":10535.1,"High":10654.6,"Low":10383.6,"Close":10630.7,"Volume":5163.484000000004}
]
Bạn có thể thấy rằng mỗi dấu ngoặc nhọn{}Bao gồm thời gian, giá mở cửa (open), giá cao nhất (high), giá thấp nhất (low), giá đóng cửa (close) và khối lượng giao dịch (volume).
Đây là một cây nến. Nhìn chung, dữ liệu đường K được sử dụng để tính toán các chỉ số như đường trung bình động MA, MACD, v.v.
Nhập dữ liệu đường K làm tham số (dữ liệu nguyên liệu thô), sau đó thiết lập các tham số chỉ báo và tính toán hàm của dữ liệu chỉ báo, mà chúng ta gọi là hàm chỉ báo.
Có nhiều chức năng chỉ báo trên Nền tảng giao dịch định lượng Inventor.
Ví dụ, khi chúng ta tính toán chỉ báo trung bình động, dựa trên các chu kỳ khác nhau của dữ liệu đường K mà chúng ta truyền vào, chúng ta sẽ tính toán trung bình động của chu kỳ tương ứng. Ví dụ, nếu dữ liệu K-line hàng ngày được truyền vào (một thanh K-line biểu thị một ngày), chỉ báo được tính toán là trung bình động hàng ngày. Tương tự, nếu dữ liệu K-line được truyền vào hàm chỉ báo trung bình động là trong khoảng thời gian 1 giờ, thì chỉ số được tính toán là đường trung bình động 1 giờ.
Thông thường khi chúng ta tính toán các chỉ số, chúng ta thường bỏ qua một vấn đề. Nếu tôi muốn tính toán chỉ số trung bình động 5 ngày, thì trước tiên chúng ta chuẩn bị dữ liệu K-line hàng ngày:
var r = exchange.GetRecords(PERIOD_D1) // 给GetRecords 函数传入参数 PERIOD_D1就是指定获取日K线,
// 具体函数使用可以参看:https://www.fmz.com/api#GetRecords
Với dữ liệu K-line hàng ngày, chúng ta có thể tính toán chỉ báo trung bình động. Nếu chúng ta muốn tính toán trung bình động 5 ngày, chúng ta cần đặt tham số chỉ báo của hàm chỉ báo thành 5.
var ma = TA.MA(r, 5) // TA.MA() 就是指标函数,用来计算均线指标,第一个参数设置刚才获取的日K线数据r,
// 第二个参数设置5,计算出来的就是5日均线,其它指标函数同理。
Chúng tôi đã bỏ qua một vấn đề tiềm ẩn. Nếu số lượng thanh K-line trong dữ liệu K-line r-day nhỏ hơn 5 thì sao? Chúng ta có thể tính toán một chỉ báo trung bình động 5 ngày hợp lệ không? Câu trả lời chắc chắn là không. Bởi vì chỉ báo trung bình động là tìm giá trị trung bình của giá đóng cửa của một số lượng thanh K-line nhất định.

Do đó, trước khi sử dụng dữ liệu K-line và các hàm chỉ báo để tính toán dữ liệu chỉ báo, cần xác định xem số cột K-line trong dữ liệu K-line có đáp ứng các điều kiện để tính toán chỉ báo hay không (tham số chỉ báo)
Do đó, trước khi tính toán đường trung bình động 5 ngày, cần phải đưa ra phán đoán. Mã đầy đủ như sau:
function CalcMA () {
var r = _C(exchange.GetRecords, PERIOD_D1) // _C() 是容错函数,目的就是避免 r 为 null , 具体可以查询文档:https://www.fmz.com/api#_C
if (r.length > 5) {
return TA.MA(r, 5) // 用均线指标函数 TA.MA 计算出均线数据,做为函数返回值,返回。
}
return false
}
function main () {
var ma = CalcMA()
Log(ma)
}

Kiểm tra ngược cho thấy: [null,null,null,null,4228.7,4402.9400000000005, … ]
Có thể thấy rằng bốn chỉ báo đầu tiên trong số các chỉ báo trung bình động 5 ngày được tính toán là không có giá trị, vì số cột đường K nhỏ hơn 5 và không thể tính được giá trị trung bình. Đến nến thứ 5, có thể tính toán được.
Khi chúng ta viết một số chiến lược, thường có một kịch bản mà chúng ta cần xử lý một số thao tác hoặc in một số nhật ký khi mỗi chu kỳ K-line hoàn tất. Làm thế nào để chúng ta đạt được chức năng này? Đối với những người mới bắt đầu không có kinh nghiệm lập trình, họ có thể không nghĩ ra được cơ chế nào để xử lý vấn đề này. Sau đây chúng tôi sẽ trực tiếp đưa ra cho bạn một số mẹo.
Chúng ta có thể đánh giá rằng một chu kỳ cột K-line được hoàn thành bằng cách bắt đầu với thuộc tính thời gian trong dữ liệu K-line. Mỗi lần chúng ta có được dữ liệu K-line, chúng ta đánh giá thuộc tính Thời gian trong dữ liệu của cột K-line cuối cùng của dữ liệu dòng K này. Giá trị thuộc tính này có thay đổi không. Nếu nó đã thay đổi, điều đó có nghĩa là một cột dòng K mới đã được tạo (chứng minh rằng chu kỳ cột dòng K trước đó của cột dòng K mới tạo đã đã hoàn tất). Nếu chưa thay đổi, nghĩa là chưa có nến mới nào được tạo ra (chu kỳ nến cuối cùng hiện tại vẫn chưa hoàn tất).
Vì vậy, chúng ta cần một biến để ghi lại thời gian của cột nến cuối cùng trong dữ liệu nến.
var r = exchange.GetRecords()
var lastTime = r[r.length - 1].Time // lastTime 用来记录最后一根K线柱的时间。
Trong ứng dụng thực tế, cấu trúc thường như sau:
function main () {
var lastTime = 0
while (true) {
var r = _C(exchange.GetRecords)
if (r[r.length - 1].Time != lastTime) {
Log("新K线柱产生")
lastTime = r[r.length - 1].Time // 一定要更新 lastTime ,这个至关重要。
// ... 其它处理逻辑
// ...
}
Sleep(500)
}
}

Có thể thấy rằng trong backtest, chu kỳ K-line được đặt thành ngày (hàm exchange.GetRecords được gọi mà không chỉ định tham số và chu kỳ K-line được đặt theo backtest là tham số mặc định). Bất cứ khi nào Cột K-line mới xuất hiện, nó in ra một bản ghi.
Nếu bạn muốn hiển thị hoặc kiểm soát thời gian cần thiết để một chiến lược truy cập vào giao diện trao đổi, bạn có thể sử dụng mã sau:
function main () {
while (true) {
var beginTime = new Date().getTime()
var ticker = exchange.GetTicker()
var endTime = new Date().getTime()
LogStatus(_D(), "GetTicker() 函数耗时:", endTime - beginTime, "毫秒")
Sleep(1000)
}
}
Nói một cách đơn giản, dấu thời gian được ghi lại sau khi gọi hàm GetTicker được trừ khỏi dấu thời gian trước khi gọi để tính số mili giây đã trôi qua, tức là thời gian hàm GetTicker thực thi và trả về kết quả.
Nếu bạn muốn giới hạn trên bằng số, bạn thường sử dụng Math.min để giới hạn
Ví dụ, khi đặt lệnh bán, số tiền đặt lệnh không được lớn hơn số coin trong tài khoản. Bởi vì nếu số tiền lớn hơn số coin có sẵn trong tài khoản, lỗi sẽ được báo cáo khi đặt lệnh.
Thường được kiểm soát như thế này: Ví dụ, bạn dự định đặt lệnh bán 0,2 xu.
var planAmount = 0.2
var account = _C(exchange.GetAccount)
var amount = Math.min(account.Stocks, planAmount)
Điều này đảm bảo rằng số tiền của lệnh sẽ không vượt quá số lượng coin có sẵn trong tài khoản.
Tương tự như vậy, Math.max được sử dụng để đảm bảo giới hạn dưới cho một giá trị. Điều này thường áp dụng cho những tình huống nào? Nhìn chung, các sàn giao dịch có giới hạn số lượng đặt hàng tối thiểu cho một số cặp giao dịch nhất định. Nếu số lượng đặt hàng thấp hơn số lượng đặt hàng tối thiểu này, lệnh sẽ bị từ chối. Lệnh này sẽ thất bại. Giả sử số lượng đặt hàng tối thiểu cho BTC thường là 0,01. Đôi khi chiến lược giao dịch có thể tính toán rằng số lượng đặt hàng nhỏ hơn 0,01, do đó chúng ta có thể sử dụng Math.max để đảm bảo số lượng đặt hàng tối thiểu.
Có thể sử dụng_Hàm N() hoặc hàm SetPrecision để kiểm soát độ chính xác.
Hàm SetPrecision() chỉ cần được thiết lập một lần và hệ thống sẽ tự động cắt bỏ những chữ số thập phân thừa trong số lượng đặt hàng và giá trị giá.
_Hàm N() được sử dụng để cắt bớt một giá trị thành một số chữ số thập phân nhất định (kiểm soát độ chính xác)
Ví dụ:
var pi = _N(3.141592653, 2)
Log(pi)
Giá trị của pi được cắt bớt thành 2 chữ số thập phân, đó là: 3,14
Xem tài liệu API để biết chi tiết.
Bạn có thể sử dụng cơ chế này để sử dụng phương pháp phát hiện dấu thời gian để xác định dấu thời gian hiện tại trừ đi dấu thời gian của lần cuối cùng tác vụ theo lịch trình được hoàn thành và tính toán thời gian đã trôi qua theo thời gian thực. Khi thời gian đã trôi qua vượt quá một độ dài thời gian nhất định đã đặt Sau đó , thao tác mới được thực hiện.
Ví dụ, nó được sử dụng trong các chiến lược đầu tư cố định.
var lastActTime = 0
var waitTime = 1000 * 60 * 60 * 12 // 一天的毫秒数
function main () {
while (true) {
var nowTime = new Date().getTime()
if (nowTime - lastActTime > waitTime) {
Log("执行定投")
// ... 具体的定投操作,买入操作。
lastActTime = nowTime
}
Sleep(500)
}
}
Đây là một ví dụ đơn giản.
Sử dụng hàm _G() lượng tử của nhà phát minh và hàm thoát lưu, rất thuận tiện để thiết kế chiến lược thoát và lưu tiến trình, cũng như khởi động lại để tự động khôi phục trạng thái.
var hold = {
price : 0,
amount : 0,
}
function main () {
if (_G("hold")) {
var ret = _G("hold")
hold.price = ret.price
hold.amount = ret.amount
Log("恢复 hold:", hold)
}
var count = 1
while (true) {
// ... 策略逻辑
// ... 策略运行中,可能开仓,交易,把开仓的持仓价格赋值给 hold.price ,开仓的数量赋值给 hold.amount,用以记录持仓信息。
hold.price = count++ // 模拟一些数值
hold.amount = count/10 // 模拟一些数值
Sleep(500)
}
}
function onexit () { // 点击机器人上的停止按钮,会触发执行这个函数,执行完毕机器人停止。
_G("hold", hold)
Log("保存 hold:", JSON.stringify(hold))
}

Có thể thấy rằng mỗi lần dừng robot, dữ liệu trong đối tượng giữ được lưu lại. Mỗi lần khởi động lại, dữ liệu được đọc và giá trị giữ được khôi phục về trạng thái dừng trước đó. Tất nhiên, ví dụ trên chỉ là một ví dụ đơn giản. Nếu sử dụng trong một chiến lược thực tế, nó phải được thiết kế theo dữ liệu chính cần được khôi phục trong chiến lược (thường là thông tin tài khoản, vị thế, giá trị lợi nhuận, hướng giao dịch, v.v.) .). Tất nhiên, bạn cũng có thể đặt một số điều kiện để xác định xem có nên khôi phục hay không.
Trên đây là một số mẹo để phát triển chiến lược. Tôi hy vọng chúng sẽ hữu ích cho người mới bắt đầu và các nhà phát triển chiến lược! Cách nhanh nhất để cải thiện là thực hành! Tôi chúc tất cả mọi người tiếp tục đạt được lợi nhuận.