机器人逻辑 — TradingView Lambda 机器人

今天我想和大家分享一个交易机器人的实现。首先我们需要一个技术任务:

  • 必须能够独立交易

让我们按预期从应用程序的架构开始:

机器人逻辑 — TradingView Lambda 机器人

所以 Lambda 将从某些东西(例如,从 Trading View)接收 WebHook。此 WebHook 只能传递两个值:BUY 或 SELL(无附加参数)。机器人将决定买卖的数量和硬币。

在数据库中,我们将只有三个实体:

策略——每个单独的“买卖”都是一个独立的策略

保持 – 保持系统(更多内容见下文)

设置 – 机器人设置,例如表名称、币安 API 密钥和秘密以及策略配置。

让我们开始实施

首先,让我们安装必要的库:

yarn add dynamoose node-binance-api typescript uuid# dynamoose - 类似于 mongoose 仅​​用于 DynamoDB 
# node-binance-api - 用于使用 Binance API 
# typescript - 我们将使用 Typescript 
# uuid - 生成唯一 ID

此外,添加类型包:

纱线添加 -D @types/node @types/uuid

让我们描述一下数据库模型:

import * as dynamoose from ‘dynamoose’
import {Document} from “dynamoose/dist/Document”;
import config from ‘../../config’
export class HoldDocument extends Document {
id?: string
type?: string
symbol?: string
status?: string
data?: string
avgPrice?: number
avgPriceProfit?: number
orderId?: string
qty?: number
createdAt?: Date
}
export default dynamoose.model<HoldDocument>(config.tables.hold, {
id: {
type: String,
hashKey: true
},
type: {
type: String,
index: {
name: “type”,
global: true
}
},
symbol: {
type: String,
index: {
name: “symbol”,
global: true
}
},
status: {
type: String,
index: {
name: “status”,
global: true
}
},
data: String,
avgPrice: Number,
avgPriceProfit: Number,
orderId: String,
qty: Number,
createdAt: Date,
}, {
// @ts-ignore-next-line
saveUnknown: false
});
view rawtw-bot-hold.ts hosted with ❤ by GitHub
import * as dynamoose from ‘dynamoose’
import config from ‘../../config’
import {Document} from “dynamoose/dist/Document”;
export class SettingDocument extends Document {
id?: string
type?: string
symbol?: string
status?: string
data?: string
createdAt?: Date
}
export default dynamoose.model<SettingDocument>(config.tables.setting, {
id: {
type: String,
hashKey: true
},
type: {
type: String,
index: {
name: “type”,
global: true
}
},
symbol: {
type: String,
index: {
name: “symbol”,
global: true
}
},
data: String,
createdAt: Date,
}, {
// @ts-ignore-next-line
saveUnknown: false,
});
import * as dynamoose from ‘dynamoose’
import config from ‘../../config’
import {Document} from “dynamoose/dist/Document”;
export class StrategyDocument extends Document {
id?: string
type?: string
symbol?: string
status?: string
profit?: number
data?: string
createdAt?: Date
holdId?: string
unHoldPrice?: number
}
export default dynamoose.model<StrategyDocument>(config.tables.strategy, {
id: {
type: String,
hashKey: true
},
type: {
type: String,
index: {
name: “type”,
global: true
}
},
symbol: {
type: String,
index: {
name: “symbol”,
global: true
}
},
status: {
type: String,
index: {
name: “status”,
global: true
}
},
profit: Number,
data: String,
createdAt: Date,
holdId: {
type: String,
index: {
name: “holdId”,
global: true
}
},
unHoldPrice: Number,
}, {
// @ts-ignore-next-line
saveUnknown: false,
});

在策略中设置设置。最重要的是:

  • 资产——我们将购买哪种资产机器人
  • type – 我们策略的唯一名称。如果我们想将我们的 lambda 用于许多策略,我们将为每个策略设置一个唯一的名称
  • riskPercent — 在一种策略中使用的 USDT 总余额的百分比
  • minAmountUSDT — 策略中使用的最低 USDT 数量,如果我们的余额较少,机器人将跳过此 BUY 事件

添加配置文件:

export default {
tables: {
hold: process.env.DYNAMODB_HOLD_TABLE as string,
setting: process.env.DYNAMODB_SETTING_TABLE as string,
strategy: process.env.DYNAMODB_STRATEGY_TABLE as string,
},
binance: {
key: process.env.BINANCE_API_KEY as string,
secret: process.env.BINANCE_API_SECRET as string
},
strategy: {
type: ‘spot’,
asset: ‘ETH’,
currency: ‘USDT’,
defaultSetting: {isReuseHold: true, riskPercent: 0.05, minAmountUSDT: 11}
}
}

以及使用数据库的提供者,该数据库具有机器人的所有必要方法。

添加用于使用 Binance API 的接口。我们只需要一些功能,如:“市价买入”、“市价卖出”、“创建限价单”、“取消订单”、“获取当前价格”、“获取我的余额”和“查看订单状态”。要实现这一点,请添加以下类:

import HoldModel, {HoldDocument} from ‘../model/hold’
import Base from ‘./base’
import {HOLD_STATUS} from ‘../../constants’
class Hold extends Base<HoldDocument, THold> {
getByTypeAndSymbol(type: string, symbol: string): Promise<THold | undefined> {
return this.getFirst({type, symbol})
}
getByTypeAndSymbolStatus(type: string, symbol: string, status: string): Promise<THold | undefined> {
return this.getFirst({type, symbol, status})
}
create(data: THold): Promise<THold> {
return super.create({
status: HOLD_STATUS.STARTED,
data
})
}
getCurrentHolds(): Promise<THold[]> {
return this.getList({status: HOLD_STATUS.STARTED})
}
}
export default new Hold(HoldModel);
import SettingModel, {SettingDocument} from ‘../model/setting’
import Base from ‘./base’
import config from ‘../../config’
class Setting extends Base<SettingDocument, TSetting> {
async getByTypeAndSymbol(type: string, symbol: string): Promise<TSetting> {
let setting = await this.getFirst({type, symbol})
if (!setting) {
setting = await this.create({
type,
symbol,
data: config.strategy.defaultSetting
})
}
return setting
}
}
export default new Setting(SettingModel);
import StrategyModel, {StrategyDocument} from ‘../model/strategy’
import Base from ‘./base’
import {STRATEGY_STATUS} from ‘../../constants’
import {getOrderPrice} from ‘../../helper/order’
class Strategy extends Base<StrategyDocument, TStrategy> {
getByTypeAndSymbol(type: string, symbol: string): Promise<TStrategy[]> {
return this.getList({type, symbol})
}
create(data: TStrategy): Promise<TStrategy> {
return super.create({
status: STRATEGY_STATUS.CREATED,
data
})
}
update(s: TStrategy): Promise<TStrategy> {
if (s.id) {
return super.update(s)
} else {
return this.create(s)
}
}
getByHoldId(type: string, symbol: string, holdId: string): Promise<TStrategy[]> {
return this.getList({type, symbol, holdId})
}
async getSimilarHold(strategy: TStrategy, currentPrice: number): Promise<TStrategy | undefined> {
const list = (await this.getList({
type: strategy.type,
symbol: strategy.symbol,
status: STRATEGY_STATUS.HOLD,
})) || []
return list.find((s: TStrategy) => currentPrice >= getOrderPrice(s.data?.buyOrder))
}
getByTypeAndSymbolStatus(type: string, symbol: string, status: string): Promise<TStrategy[]> {
return this.getList({type, symbol, status})
}
getCurrentStrategy(type: string, symbol: string): Promise<TStrategy | undefined> {
return this.getFirst({type, symbol, status: STRATEGY_STATUS.STARTED})
}
}
export default new Strategy(StrategyModel);

让我们为一个不会赔钱的机器人创建一个策略。

机器人逻辑 — TradingView Lambda 机器人

例如,机器人用 2000 USDT 购买了 0.1 ETH。然后是卖出信号。有两种可能的结果:

  1. 如果当前价格高于买入价格(例如,2200 USDT),则机器人卖出并计算利润:(2200-2000)* 0.1 = 20USDT(策略状态 = FINISHED)
  2. 如果当前价格低于购买价格,机器人会记住并持有此购买(不要亏本出售)。(策略状态=持有)

我们还创建了一个 Hold 实体,它将监控处于 HOLD 状态的所有策略,并下达限价单,以所有策略的平均买入价卖出。

例如,1900 USDT 又购买了 0.05 ETH。当前价格再次低于买入价。该策略也将被搁置。机器人将重新计算两种策略的平均持有价格 (0.1*2000 + 0.05*1900)/0.15 = 1967 USDT 并下达限价卖单。如果这个订单被执行,我们不会亏损,也不会获得利润,但我们会退还我们的 USDT。

每隔 5 分钟,机器人会检查该卖单,一旦成交,机器人会将 HOLD 策略的所有状态更改为 UNHOLD 状态。

您还可以在 HOLD 状态下添加策略的重用。如果 BUY 信号到达,当前价格为 1950USDT,我们有一个持有状态的策略,买入价为 1900USDT。机器人不会再次购买。它将购买订单数据复制到新策略中,取消之前的策略并将其从保留中移除。

现在我们可以继续编写机器人的逻辑了。处理买入信号的主要功能是:

async buy(currency: string): Promise<void> {
// reuse strategy with status HOLD if exists
if (this.setting.data.isReuseHold) {
const currentPrice = await this.getPrice()
const similar = await strategyProvider.getSimilarHold(this.strategy, currentPrice)
if (similar && similar.id) {
if (await this.removeFromHold(similar)) {
this.strategy = await this.copyStrategy(this.strategy, similar)
return
}
}
}
// calculate qty and buy
const qty = await this.calculatePositionQty(this.symbol, currency)
const buyOrder = await this.marketBuy(qty) as any
const {avgPrice, totalQty, commission, commissionAsset} = calculateOrderFills(buyOrder && buyOrder.fills)
buyOrder.commission = commission
buyOrder.commissionAsset = commissionAsset
buyOrder.avgPrice = avgPrice
buyOrder.totalQty = totalQty
this.setData({buyOrder})
this.strategy.status = STRATEGY_STATUS.STARTED
// save strategy to DB
await strategyProvider.update(this.strategy)
}

以及处理卖出信号的主要功能:

async sell(): Promise<void> {
// get current price
const currentPrice = await this.getPrice()
const buyPrice = getOrderPrice(this.strategy?.data?.buyOrder)
const buyQty = getOrderQuantity(this.strategy?.data?.buyOrder)
if (buyPrice < currentPrice) {
// if buyPrice < currentPrice then sell this amount to profit
if (buyQty > 0) {
const sellOrder = await this.marketSell(buyQty) as any
const {
avgPrice,
totalQty,
commission,
commissionAsset
} = calculateOrderFills(sellOrder && sellOrder.fills)
sellOrder.avgPrice = avgPrice
sellOrder.totalQty = totalQty
sellOrder.commission = commission
sellOrder.commissionAsset = commissionAsset
this.strategy.profit = calculateProfit(this.strategy?.data?.buyOrder, sellOrder)
this.setData({sellOrder})
this.strategy.status = STRATEGY_STATUS.FINISHED
await strategyProvider.update(this.strategy)
}
} else {
// if buyPrice >= currentPrice then hold this strategy
if (buyQty > 0) {
await this.addToHold()
}
}
}

当我们添加一个持有策略时会发生什么的更多细节:

async addToHold(): Promise<void> {
// get or create Hold
let hold = await holdProvider.getByTypeAndSymbolStatus(this.type, this.symbol, HOLD_STATUS.STARTED)
if (!hold) {
hold = await holdProvider.create({
type: this.type,
symbol: this.symbol,
status: HOLD_STATUS.STARTED
})
}
// add status and holdId to strategy
this.strategy.holdId = hold.id
this.strategy.status = STRATEGY_STATUS.HOLD
await strategyProvider.update(this.strategy)
// recalculate averageHoldPrice
const calcHold = await this.recalculateHold(hold)
if (calcHold) {
// create or move hold limit sell order
await this.createOrUpdateOrder(calcHold)
}
}

最后缺少的是对 Hold 下的限价卖单每 5 分钟进行一次检查。让我们添加这个检查:

class CheckHold {
api: BaseApiSpotService
hold: THold
constructor(hold: THold) {
this.hold = hold
this.api = new BaseApiSpotService(this.hold.symbol)
}
async check() {
if (this.hold.orderId) {
await this.checkOrder()
if (getOrderStatus(this.hold.data.sellOrder) === ORDER.STATUS.FILLED) {
await this.unHold()
}
}
}
async checkOrder(): Promise<void> {
const orderData: any = await this.api.checkStatus(this.hold.orderId!)
if (!this.hold.data) {
this.hold.data = {}
}
// update hold data if order changed
if (orderData && orderData.orderId && (orderData.orderId!==this.hold.orderId || getOrderStatus(this.hold.data.sellOrder) !== String(orderData.status))) {
this.hold.data.sellOrder = orderData
const {avgPrice, totalQty, commission, commissionAsset} = calculateOrderFills(
orderData && orderData.fills)
this.hold.data.sellOrder.totalQty = totalQty
this.hold.data.sellOrder.commission = commission
this.hold.data.sellOrder.avgPrice = avgPrice
this.hold.data.sellOrder.commissionAsset = commissionAsset
await holdProvider.update(this.hold)
}
}
async unHold(): Promise<void> {
// get all strategies which related with this hold
const strategies = await strategyProvider.getByHoldId(this.hold.type, this.hold.symbol, this.hold.id!)
if (strategies && strategies.length > 0) {
for (const s of strategies) {
try {
// calculate profit for each strategy
const qty = getOrderQuantity(s.data?.buyOrder)
const commission = this.hold.data.sellOrder.totalQty > 0 ? this.hold.data.sellOrder.commission * qty /
this.hold.data.sellOrder.totalQty : 0
const sell = {this.hold.data.sellOrder, totalQty: qty, commission}
s.profit = calculateProfit(s.data?.buyOrder, sell)
s.data = {s.data, sellOrder: sell}
// save UNHOLD status, profit, and data to DB
await strategyProvider.update({s, status: STRATEGY_STATUS.UNHOLD})
} catch (e) {
console.log(‘error set profit’, {s, hold: this.hold})
}
}
}
// update hold status to FINISHED
this.hold.status = HOLD_STATUS.FINISHED
await holdProvider.update(this.hold)
}
}

结论

在本文中,我们学习了如何实现 TradingView Lambda 机器人的基本逻辑,该机器人只能用于盈利。但它还没有完成。下次我们将通过测试来介绍机器人。文章的下一部分在这里

给TA打赏
共{{data.count}}人
人已打赏
量化交易

如何改进机器学习模型的交易策略

2022-4-27 19:32:04

量化交易

OCO和LEOCO交易——最安全、最高效的半自动交易策略

2022-9-5 20:52:44

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索