[TOC]

Các nhà giao dịch thường xuyên sử dụng TradingView đều biết rằng TradingView có thể đẩy tin nhắn đến các nền tảng khác. Trước đây, một chiến lược đẩy tín hiệu TradingView đã được công bố trong thư viện tài liệu. Nội dung của tin nhắn được đẩy đã được mã hóa cứng trong URL yêu cầu, có phần không linh hoạt. Trong bài viết này, chúng tôi sẽ thiết kế lại chiến lược thực hiện tín hiệu TradingView theo một cách khác.
Một số học viên mới có thể bối rối khi nhìn thấy tiêu đề của bài viết này và phần mô tả ở trên, nhưng điều đó không quan trọng! Trước tiên chúng ta hãy làm rõ các nguyên tắc và kịch bản nhu cầu. Cho bạn biết tôi đang nói về điều gì. Được rồi, chúng ta hãy vào vấn đề chính.
Kịch bản nhu cầu: Sau tất cả những lời bàn tán này, thứ này được cho là có tác dụng gì? Nói một cách đơn giản, chúng ta có nhiều chỉ báo, chiến lược, mã, v.v. mà chúng ta có thể chọn sử dụng trên TradingView. Những chỉ báo này có thể chạy trực tiếp trên TradingView và có thể vẽ đường, tính toán, hiển thị tín hiệu giao dịch, v.v. Ngoài ra, TradingView còn có dữ liệu giá theo thời gian thực và dữ liệu K-line đầy đủ để tạo điều kiện thuận lợi cho việc tính toán nhiều chỉ số khác nhau. Các mã lệnh này trên TradingView được gọi là ngôn ngữ PINE. Sự bất tiện duy nhất là giao dịch thực tế trên TradingView. Mặc dù ngôn ngữ PINE đã được hỗ trợ trên FMZ nhưng nó vẫn có thể chạy theo thời gian thực. Tuy nhiên, cũng có những người hâm mộ trung thành của TradingView vẫn hy vọng đặt lệnh dựa trên các tín hiệu được gửi bởi biểu đồ trên TradingView. Nhu cầu này cũng có thể được giải quyết bằng FMZ. Vì vậy, bài viết này sẽ giải thích chi tiết cụ thể về giải pháp này.
Nguyên tắc:

Toàn bộ kế hoạch bao gồm bốn nội dung chính, tóm lại như sau:
| số seri | thân chính | mô tả |
|---|---|---|
| 1 | TradingView (Trading View trong hình) | TradingView chạy tập lệnh PINE, có thể gửi tín hiệu và truy cập vào giao diện API mở rộng của FMZ. |
| 2 | Nền tảng FMZ (Nền tảng FMZ (trang web) trong hình) | Quản lý thị trường thực, gửi hướng dẫn tương tác trên trang thị trường thực và cũng sử dụng giao diện API mở rộng để cho phép nền tảng FMZ gửi hướng dẫn tương tác đến chương trình chiến lược thị trường thực trên người giám hộ |
| 3 | Chương trình thực tế trên phần mềm lưu trữ (Robot chiến lược FMZ trong hình) | Chương trình thực tế thực hiện chiến lược thực hiện tín hiệu TradingView |
| 4 | Trao đổi (trao đổi trong hình) | Sàn giao dịch được cấu hình trên thị trường thực, sàn giao dịch mà chương trình thị trường thực trên đơn vị lưu ký gửi trực tiếp yêu cầu đặt lệnh |
Vì vậy, nếu bạn muốn chơi như thế này, bạn cần chuẩn bị những điều sau: 1. Tập lệnh chạy trên TradingView chịu trách nhiệm gửi yêu cầu tín hiệu đến giao diện API mở rộng của FMZ. Tài khoản TradingView phải là thành viên PRO trở lên. 2. Triển khai chương trình giám sát trên FMZ, chương trình này cần có khả năng truy cập vào giao diện trao đổi (như máy chủ ở Singapore, Nhật Bản, Hồng Kông, v.v.). 3. Cấu hình API KEY của sàn giao dịch trên FMZ để hoạt động (đặt lệnh) khi tín hiệu TradingView được gửi đi. 4. Bạn cần phải có “chiến lược thực hiện tín hiệu TradingView”, đây chính là nội dung chính mà bài viết này sẽ đề cập đến.
Phiên bản trước của “Chiến lược thực hiện tín hiệu TradingView” không được thiết kế để linh hoạt và thông báo chỉ có thể được mã hóa cứng trong URL của yêu cầu do TradingView gửi. Nếu chúng ta muốn TradingView ghi một số thông tin biến đổi vào phần Nội dung khi đẩy tin nhắn, thì hiện tại chúng ta không thể làm gì được. Ví dụ, nội dung tin nhắn trên TradingView trông như thế này:

Sau đó, có thể thiết lập TradingView như trong hình minh họa, viết tin nhắn vào phần nội dung yêu cầu và gửi đến giao diện API mở rộng của FMZ. Vậy bạn gọi giao diện API mở rộng của FMZ này là gì?
Trong số các giao diện API mở rộng của FMZ, chúng tôi sẽ sử dụngCommandRobotGiao diện này thường được gọi như thế này:
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[186515,"ok12345"]
URL yêu cầu nàyqueryTRONGaccess_keyVàsecret_keyĐây là phần mở rộng của nền tảng FMZAPI KEY, ở đây chúng tôi trình bày để thiết lập nó thànhxxxVàyyyy. Làm thế nào để tạo KHÓA này? Trên trang này:https://www.fmz.com/m/account, chỉ cần tạo một cái, giữ gìn cẩn thận và không bao giờ tiết lộ nó.

Quay lại chủ đề, chúng ta hãy tiếp tụcCommandRobotVấn đề về giao diện. Nếu bạn cần truy cậpCommandRobotGiao diện, trong yêu cầumethodChỉ cần thiết lập thành:CommandRobot。CommandRobotChức năng của giao diện này là gửi một thông điệp tương tác đến một đĩa thực có ID nhất định thông qua nền tảng FMZ, do đó các tham sốargsNội dung của yêu cầu là ID thực và tin nhắn. Ví dụ URL yêu cầu ở trên là gửi yêu cầu đến ID186515Chương trình thực tế, gửi tin nhắnok12345。
Trước đây, phương pháp này được sử dụng để yêu cầu giao diện CommandRobot của API mở rộng FMZ. Tin nhắn chỉ có thể được mã hóa cứng, chẳng hạn như trong ví dụ trên.ok12345. Nếu tin nhắn nằm trong nội dung yêu cầu, cần có phương pháp khác:
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyyy&method=CommandRobot&args=[130350,+""]
Theo cách này, yêu cầu có thể được gửi qua nền tảng FMZ và nội dung của yêu cầu được gửi dưới dạng tin nhắn tương tác tới người dùng có ID130350Sự thật là vậy. Nếu thông báo trên TradingView được đặt thành:{"close": {{close}}, "name": "aaa"}, thì ID là130350Đĩa thực sẽ nhận được hướng dẫn tương tác:{"close": 39773.75, "name": "aaa"}
Để “Chiến lược thực hiện tín hiệu TradingView” hiểu đúng lệnh được TradingView gửi khi nhận được lệnh tương tác, định dạng tin nhắn phải được thỏa thuận trước:
{
Flag: "45M103Buy", // 标识,可随意指定
Exchange: 1, // 指定交易所交易对
Currency: "BTC_USDT", // 交易对
ContractType: "swap", // 合约类型,swap,quarter,next_quarter,现货填写spot
Price: "{{close}}", // 开仓或者平仓价格,-1为市价
Action: "buy", // 交易类型[ buy:现货买入 , sell:现货卖出 , long:期货做多 , short:期货做空 , closesell:期货买入平空 , closebuy:期货卖出平多]
Amount: "0", // 交易量
}
Chiến lược này được thiết kế theo kiến trúc đa sàn giao dịch, do đó nhiều đối tượng sàn giao dịch có thể được cấu hình trên chiến lược này, nghĩa là có thể kiểm soát các hoạt động đặt hàng của nhiều tài khoản khác nhau. Chỉ cần sử dụng Exchange trong cấu trúc tín hiệu để chỉ định exchange cần vận hành. Đặt thành 1 có nghĩa là tín hiệu này sẽ vận hành tài khoản exchange tương ứng với đối tượng exchange được thêm đầu tiên. Nếu hợp đồng giao ngay được vận hành, hãy đặt ContractType thành giao ngay; đối với hợp đồng tương lai, hãy viết hợp đồng cụ thể, ví dụ, viết hoán đổi cho hợp đồng vĩnh viễn. Đối với giá lệnh thị trường, chỉ cần nhập -1. Cài đặt hành động khác nhau đối với hợp đồng tương lai, giao ngay, vị thế mở và đóng và không thể cài đặt sai.
Tiếp theo, bạn có thể thiết kế mã chiến lược. Mã chiến lược đầy đủ là:
//信号结构
var Template = {
Flag: "45M103Buy", // 标识,可随意指定
Exchange: 1, // 指定交易所交易对
Currency: "BTC_USDT", // 交易对
ContractType: "swap", // 合约类型,swap,quarter,next_quarter,现货填写spot
Price: "{{close}}", // 开仓或者平仓价格,-1为市价
Action: "buy", // 交易类型[ buy:现货买入 , sell:现货卖出 , long:期货做多 , short:期货做空 , closesell:期货买入平空 , closebuy:期货卖出平多]
Amount: "0", // 交易量
}
var BaseUrl = "https://www.fmz.com/api/v1" // FMZ扩展API接口地址
var RobotId = _G() // 当前实盘ID
var Success = "#5cb85c" // 成功颜色
var Danger = "#ff0000" // 危险颜色
var Warning = "#f0ad4e" // 警告颜色
var buffSignal = []
// 校验信号消息格式
function DiffObject(object1, object2) {
const keys1 = Object.keys(object1)
const keys2 = Object.keys(object2)
if (keys1.length !== keys2.length) {
return false
}
for (let i = 0; i < keys1.length; i++) {
if (keys1[i] !== keys2[i]) {
return false
}
}
return true
}
function CheckSignal(Signal) {
Signal.Price = parseFloat(Signal.Price)
Signal.Amount = parseFloat(Signal.Amount)
if (Signal.Exchange <= 0 || !Number.isInteger(Signal.Exchange)) {
Log("交易所最小编号为1,并且为整数", Danger)
return
}
if (Signal.Amount <= 0 || typeof(Signal.Amount) != "number") {
Log("交易量不能小于0,并且为数值类型", typeof(Signal.Amount), Danger)
return
}
if (typeof(Signal.Price) != "number") {
Log("价格必须是数值", Danger)
return
}
if (Signal.ContractType == "spot" && Signal.Action != "buy" && Signal.Action != "sell") {
Log("指令为操作现货,Action错误,Action:", Signal.Action, Danger)
return
}
if (Signal.ContractType != "spot" && Signal.Action != "long" && Signal.Action != "short" && Signal.Action != "closesell" && Signal.Action != "closebuy") {
Log("指令为操作期货,Action错误,Action:", Signal.Action, Danger)
return
}
return true
}
function commandRobot(url, accessKey, secretKey, robotId, cmd) {
// https://www.fmz.com/api/v1?access_key=xxx&secret_key=xxx&method=CommandRobot&args=[xxx,+""]
url = url + '?access_key=' + accessKey + '&secret_key=' + secretKey + '&method=CommandRobot&args=[' + robotId + ',+""]'
var postData = {
method:'POST',
data:cmd
}
var headers = "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36\nContent-Type: application/json"
var ret = HttpQuery(url, postData, "", headers)
Log("模拟TradingView的webhook请求,发送用于测试的POST请求:", url, "body:", cmd, "应答:", ret)
}
function createManager() {
var self = {}
self.tasks = []
self.process = function() {
var processed = 0
if (self.tasks.length > 0) {
_.each(self.tasks, function(task) {
if (!task.finished) {
processed++
self.pollTask(task)
}
})
if (processed == 0) {
self.tasks = []
}
}
}
self.newTask = function(signal) {
// {"Flag":"45M103Buy","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"10000","Action":"buy","Amount":"0"}
var task = {}
task.Flag = signal["Flag"]
task.Exchange = signal["Exchange"]
task.Currency = signal["Currency"]
task.ContractType = signal["ContractType"]
task.Price = signal["Price"]
task.Action = signal["Action"]
task.Amount = signal["Amount"]
task.exchangeIdx = signal["Exchange"] - 1
task.pricePrecision = null
task.amountPrecision = null
task.error = null
task.exchangeLabel = exchanges[task.exchangeIdx].GetLabel()
task.finished = false
Log("创建任务:", task)
self.tasks.push(task)
}
self.getPrecision = function(n) {
var precision = null
var arr = n.toString().split(".")
if (arr.length == 1) {
precision = 0
} else if (arr.length == 2) {
precision = arr[1].length
}
return precision
}
self.pollTask = function(task) {
var e = exchanges[task.exchangeIdx]
var name = e.GetName()
var isFutures = true
e.SetCurrency(task.Currency)
if (task.ContractType != "spot" && name.indexOf("Futures_") != -1) {
// 非现货,则设置合约
e.SetContractType(task.ContractType)
} else if (task.ContractType == "spot" && name.indexOf("Futures_") == -1) {
isFutures = false
} else {
task.error = "指令中的ContractType与配置的交易所对象类型不匹配"
return
}
var depth = e.GetDepth()
if (!depth || !depth.Bids || !depth.Asks) {
task.error = "订单薄数据异常"
return
}
if (depth.Bids.length == 0 && depth.Asks.length == 0) {
task.error = "盘口无订单"
return
}
_.each([depth.Bids, depth.Asks], function(arr) {
_.each(arr, function(order) {
var pricePrecision = self.getPrecision(order.Price)
var amountPrecision = self.getPrecision(order.Amount)
if (Number.isInteger(pricePrecision) && !Number.isInteger(self.pricePrecision)) {
self.pricePrecision = pricePrecision
} else if (Number.isInteger(self.pricePrecision) && Number.isInteger(pricePrecision) && pricePrecision > self.pricePrecision) {
self.pricePrecision = pricePrecision
}
if (Number.isInteger(amountPrecision) && !Number.isInteger(self.amountPrecision)) {
self.amountPrecision = amountPrecision
} else if (Number.isInteger(self.amountPrecision) && Number.isInteger(amountPrecision) && amountPrecision > self.amountPrecision) {
self.amountPrecision = amountPrecision
}
})
})
if (!Number.isInteger(self.pricePrecision) || !Number.isInteger(self.amountPrecision)) {
task.err = "获取精度失败"
return
}
e.SetPrecision(self.pricePrecision, self.amountPrecision)
// buy:现货买入 , sell:现货卖出 , long:期货做多 , short:期货做空 , closesell:期货买入平空 , closebuy:期货卖出平多
var direction = null
var tradeFunc = null
if (isFutures) {
switch (task.Action) {
case "long":
direction = "buy"
tradeFunc = e.Buy
break
case "short":
direction = "sell"
tradeFunc = e.Sell
break
case "closesell":
direction = "closesell"
tradeFunc = e.Buy
break
case "closebuy":
direction = "closebuy"
tradeFunc = e.Sell
break
}
if (!direction || !tradeFunc) {
task.error = "交易方向错误:" + task.Action
return
}
e.SetDirection(direction)
} else {
if (task.Action == "buy") {
tradeFunc = e.Buy
} else if (task.Action == "sell") {
tradeFunc = e.Sell
} else {
task.error = "交易方向错误:" + task.Action
return
}
}
var id = tradeFunc(task.Price, task.Amount)
if (!id) {
task.error = "下单失败"
}
task.finished = true
}
return self
}
var manager = createManager()
function HandleCommand(signal) {
// 检测是否收到交互指令
if (signal) {
Log("收到交互指令:", signal) // 收到交互指令,打印交互指令
} else {
return // 没有收到时直接返回,不做处理
}
// 检测交互指令是否是测试指令,测试指令可以由当前策略交互控件发出来进行测试
if (signal.indexOf("TestSignal") != -1) {
signal = signal.replace("TestSignal:", "")
// 调用FMZ扩展API接口,模拟Trading View的webhook,交互按钮TestSignal发送的消息:{"Flag":"45M103Buy","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"10000","Action":"buy","Amount":"0"}
commandRobot(BaseUrl, FMZ_AccessKey, FMZ_SecretKey, RobotId, signal)
} else if (signal.indexOf("evalCode") != -1) {
var js = signal.split(':', 2)[1]
Log("执行调试代码:", js)
eval(js)
} else {
// 处理信号指令
objSignal = JSON.parse(signal)
if (DiffObject(Template, objSignal)) {
Log("接收到交易信号指令:", objSignal)
buffSignal.push(objSignal)
// 检查交易量、交易所编号
if (!CheckSignal(objSignal)) {
return
}
// 创建任务
manager.newTask(objSignal)
} else {
Log("指令无法识别", signal)
}
}
}
function main() {
Log("WebHook地址:", "https://www.fmz.com/api/v1?access_key=" + FMZ_AccessKey + "&secret_key=" + FMZ_SecretKey + "&method=CommandRobot&args=[" + RobotId + ',+""]', Danger)
Log("交易类型[ buy:现货买入 , sell:现货卖出 , long:期货做多 , short:期货做空 , closesell:期货买入平空 , closebuy:期货卖出平多]", Danger)
Log("指令模板:", JSON.stringify(Template), Danger)
while (true) {
try {
// 处理交互
HandleCommand(GetCommand())
// 处理任务
manager.process()
if (buffSignal.length > maxBuffSignalRowDisplay) {
buffSignal.shift()
}
var buffSignalTbl = {
"type" : "table",
"title" : "信号记录",
"cols" : ["Flag", "Exchange", "Currency", "ContractType", "Price", "Action", "Amount"],
"rows" : []
}
for (var i = buffSignal.length - 1 ; i >= 0 ; i--) {
buffSignalTbl.rows.push([buffSignal[i].Flag, buffSignal[i].Exchange, buffSignal[i].Currency, buffSignal[i].ContractType, buffSignal[i].Price, buffSignal[i].Action, buffSignal[i].Amount])
}
LogStatus(_D(), "\n", "`" + JSON.stringify(buffSignalTbl) + "`")
Sleep(1000 * SleepInterval)
} catch (error) {
Log("e.name:", error.name, "e.stack:", error.stack, "e.message:", error.message)
Sleep(1000 * 10)
}
}
}
Các tham số và tương tác chiến lược:

「Chiến lược thực hiện tín hiệu TradingView」Địa chỉ chiến lược đầy đủ: https://www.fmz.com/strategy/392048
Trước khi chạy chiến lược, bạn phải cấu hình đối tượng trao đổi và đặt hai tham số “AccessKey của nền tảng FMZ” và “SecretKey của nền tảng FMZ” trong các tham số chiến lược. Đảm bảo rằng bạn không đặt chúng sai. Chạy nó sẽ cho thấy:

Những thông tin sau đây sẽ được in ra theo trình tự: địa chỉ WebHook cần điền vào TradingView, hướng dẫn Hành động được hỗ trợ và định dạng tin nhắn. Điều quan trọng là địa chỉ WebHook:
https://www.fmz.com/api/v1?access_key=22903bab96b26584dc5a22522984df42&secret_key=73f8ba01014023117cbd30cb9d849bfc&method=CommandRobot&args=[505628,+""]
Chỉ cần sao chép, dán và viết vào vị trí tương ứng trên TradingView.
Nếu bạn muốn mô phỏng việc TradingView gửi tín hiệu, bạn có thể nhấp vào nút TestSignal trên tương tác chiến lược:

Chiến lược này sẽ tự động gửi yêu cầu (mô phỏng TradingView gửi yêu cầu tín hiệu), gọi giao diện API mở rộng FMZ và gửi tin nhắn đến chính chiến lược đó:
{"Flag":"45M103Buy","Exchange":1,"Currency":"BTC_USDT","ContractType":"swap","Price":"16000","Action":"buy","Amount":"1"}
Chiến lược hiện tại sẽ nhận được một thông báo tương tác khác và thực hiện:

Và đặt hàng.
Để sử dụng thử nghiệm TradingView, bạn cần có tài khoản TradingView cấp độ Pro. Trước khi thử nghiệm, có một số kiến thức tiên quyết cần được giải thích ngắn gọn.
Lấy một tập lệnh PINE đơn giản (tôi tìm thấy nó trên TradingView và sửa đổi nó một chút) làm ví dụ
//@version=5
strategy("Consecutive Up/Down Strategy", overlay=true)
consecutiveBarsUp = input(3)
consecutiveBarsDown = input(3)
price = close
ups = 0.0
ups := price > price[1] ? nz(ups[1]) + 1 : 0
dns = 0.0
dns := price < price[1] ? nz(dns[1]) + 1 : 0
if (not barstate.ishistory and ups >= consecutiveBarsUp and strategy.position_size <= 0)
action = strategy.position_size < 0 ? "closesell" : "long"
strategy.order("ConsUpLE", strategy.long, 1, comment=action)
if (not barstate.ishistory and dns >= consecutiveBarsDown and strategy.position_size >= 0)
action = strategy.position_size > 0 ? "closebuy" : "short"
strategy.order("ConsDnSE", strategy.short, 1, comment=action)
Sau đây là các chỗ giữ chỗ. Ví dụ, tôi đã viết trong hộp “Tin nhắn” trong báo thức:{{strategy.order.contracts}}, sau đó khi một lệnh được kích hoạt, một tin nhắn sẽ được gửi (dựa trên các thiết lập về báo thức, đẩy email, yêu cầu URL webhook, cửa sổ bật lên, v.v.) và tin nhắn sẽ bao gồm số lượng lệnh được thực hiện này thời gian.
{{strategy.position_size}} - Trả về giá trị của cùng một từ khóa trong Pine, đó là kích thước của vị trí hiện tại.
{{strategy.order.action}} - Trả về chuỗi “mua” hoặc “bán” cho lệnh đã thực hiện.
{{strategy.order.contracts}} - Trả về số lượng hợp đồng cho lệnh đã thực hiện.
{{strategy.order.price}} - Trả về mức giá mà lệnh được thực hiện.
{{strategy.order.id}} - Trả về ID của lệnh đã thực thi (một chuỗi được sử dụng làm đối số đầu tiên trong một trong các lệnh gọi hàm tạo ra lệnh: strategy.entry, strategy.exit hoặc strategy.order).
{{strategy.order.comment}} - Trả về bình luận về lệnh đã thực thi (chuỗi được sử dụng trong tham số bình luận ở một trong các lệnh gọi hàm tạo ra lệnh: strategy.entry, strategy.exit hoặc strategy.order). Nếu không chỉ định chú thích nào, giá trị của strategy.order.id sẽ được sử dụng.
{{strategy.order.alert_message}} - Trả về giá trị của tham số alert_message, có thể được sử dụng trong mã Pine của chiến lược khi gọi một trong các hàm để đặt lệnh: strategy.entry, strategy.exit hoặc strategy.order. Tính năng này chỉ được hỗ trợ trong Pine v4.
{{strategy.market_position}} - Trả về vị trí hiện tại của chiến lược dưới dạng chuỗi: “dài”, “phẳng” hoặc “ngắn”.
{{strategy.market_position_size}} - Trả về kích thước của vị trí hiện tại dưới dạng giá trị tuyệt đối (tức là số không âm).
{{strategy.prev_market_position}} - Trả về vị trí cuối cùng của chiến lược dưới dạng chuỗi: “dài”, “phẳng” hoặc “ngắn”.
{{strategy.prev_market_position_size}} - Trả về kích thước của vị trí trước đó dưới dạng giá trị tuyệt đối (tức là số không âm).
{
"Flag":"{{strategy.order.id}}",
"Exchange":1,
"Currency":"BTC_USDT",
"ContractType":"swap",
"Price":"-1",
"Action":"{{strategy.order.comment}}",
"Amount":"{{strategy.order.contracts}}"
}

Khi tập lệnh PINE trên TradingView kích hoạt hành động giao dịch, yêu cầu url webhook sẽ được gửi đi.


Giao dịch thực tế của FMZ sẽ thực hiện tín hiệu này.


Video của Xigua: https://www.ixigua.com/7172134169580372513?utm_source=xiguastudio Đài B: https://www.bilibili.com/video/BV1BY411d7c6/ Chí Hồ: https://www.zhihu.com/zvideo/1581722694294487040
Các mã trong bài viết chỉ mang tính chất tham khảo. Bạn có thể điều chỉnh và mở rộng chúng để sử dụng thực tế.