If you are a freelancer or CTO of a financial project, sooner or later you will be faced with the issue of connecting schedules, I will save you at least a day of work. Those who are already using this library may find something new.
The article will be in the format of a "recipe book" with open source solutions for the cryptocurrency exchange Binance and Forex.
Hello, Habr!
TradingView (charting_library) , - , TradingView.com. " " .
Cook book
. , , :)
. , , 404 , , .
. โ .
, . Forex- , . 2- Forex , - . . .
GitHub, :
4 . 3 . , .
,
.
// Nodejs
import { widget } from '../public/charting_library/charting_library.min'
const widget = new widget({ <options> })
charting_library library_path: '/charting_library/'
. Vuejs vue.config.js => publicPath: '/'
. : /public/index.html
, /public/charting_library/
, .
. , : JS API UDF. "" . JSAPI, UDF , , .
- JS API โ
- UDF โ
JSAPI UDF, UDF WebSocket . , : datafeed: new Datafeeds.UDFCompatibleDatafeed('http://localhost:3000/datafeed', 1000)
TradingView JS API adapter
, , console.log('[< >]: Method call')
.
: onReady => resolveSymbol => getBars => subscribeBars => unsubscribeBars.
, , unsubscribeBars, , WebSocket . subscribeBars, unsubscribeBars . getServerTime , , .
, resolveSymbol โ has_no_volume: true.
export default {
// ,
onReady: (callback) => {
console.log('[onReady]: Method call');
// setTimeout(() => callback(< >))
},
/*
// ,
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
},
*/
//
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
// onSymbolResolvedCallback({ ..., has_no_volume: true})
},
//
getBars: (symbolInfo, interval, from, to, onHistoryCallback, onErrorCallback, firstDataRequest) => {
console.log('[getBars] Method call', symbolInfo, interval)
console.log('[getBars] First request', firstDataRequest)
},
// WebSocket
subscribeBars: (symbolInfo, interval, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscribeUID:', subscribeUID);
},
//
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
},
getServerTime: (callback) => {}
};
, Binance, .
JS API |
TradingView UDF adapter
UDF , . datafeed: new Datafeeds.UDFCompatibleDatafeed('http://localhost:3000/datafeed', 1000)
// **Fastify**
// main.js
const app = Fastify()
app.register(import('./modules/tradingview'), {})
// tradingview.js
const plugin = async (app, options) => {
//
app.get('/', (req, res) => {
res.code(200).header('Content-Type', 'text/plain')
.send('Welcome to UDF Adapter for TradingView. See ./config for more details.')
})
//
app.get('/time', (req, res) => {
console.log('[time]: Method call')
const time = Math.floor(Date.now() / 1000) // In seconds
res.code(200).header('Content-Type', 'text/plain').send(time.toString())
})
// onReady
// https://github.com/tradingview/charting_library/wiki/UDF#data-feed-configuration-data
app.get('/config', (req, res) => {
console.log('[config]: Method call')
})
// : supports_group_request: true & supports_search: false
app.get('/symbol_info', async (req, res) => {
console.log('[symbol_info]: Method call')
})
// : supports_group_request: false & supports_search: true
app.get('/symbols', async (req, res) => {
console.log('[symbol_info]: Method call')
const symbol = await getSymbols(req.query.symbol)
return symbol
})
// getBars,
app.get('/history', async (req, res) => {
console.log('[history]: Method call')
})
}
JS API getBars
, "" . getBars firstDataRequest, true\false
, . true
.
getBars: (symbolInfo, interval, from, to, onHistoryCallback, onErrorCallback, firstDataRequest) => {
console.log('[getBars] Method call', symbolInfo, interval)
console.log('[getBars] First request', firstDataRequest)
if (firstDataRequest) {
console.log('do something')
}
},
WebSocket
UDF , . JS API , setInterval subscribeBars .
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscribeUID:', subscribeUID)
window.interval = setInterval(function () {
getLastKline(symbolInfo.ticker, resolution).then(kline => onRealtimeCallback(kline))
}, 1000 * 60) // 60s update interval
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID)
clearInterval(window.interval)
console.log('[unsubscribeBars]: cleared')
}
: theme: "Light" || "Dark"
. . , , header_widget ( , .), .css.
: custom_css_url: '/tradingview.css'
, /
โ index.html
. :
.chart-controls-bar {
border-top: none !important;
}
.chart-page, .group-wWM3zP_M- {
background: transparent !important;
}
.pane-separator {
display: none !important;
}
"".
Save\Load
, , . , widget.save(cb => this.setOverlay(cb))
, .
Save\Load adapter
UDF adapter. \ .
- ,
, , , . . .
, , onChartReady. , , Observer.
widget.onChartReady(function() {
// It's now safe to call any other methods of the widget
});
TradingView.com
, .
, , . Vuejs, .
import orders from '../../../multiblock/orders/mixin'
import createOrder from './createOrder'
import openOrders from './openOrders'
import trades from './trades'
export default {
mixins: [orders, createOrder, openOrders, trades],
data: () => ({
lines: new Map()
}),
watch: {
onChartReady(val) {
if (val) {
//* Uncomment: Testing price line
// this.line({ id: 'test', price: 0.021, quantity: 100 })
}
},
},
methods: {
// Line: open orders
positionLine(data) {
this.line(data)
.onCancel(() => {
this.deleteLine(data.id)
this.$bus.$emit('market-orders-deleteOrder', data.id)
})
.onMove(() => this.$bus.$emit('market-orders-updateOrder', { id: data.id, price: this.lines.get(data.id).getPrice() }))
},
// Line: order mobule ('price', 'stopPrice')
orderLine({ id = 'price', ...data }) {
this.line({ id, ...data })
.onMove(() => {
// Set new value on draging
this.$store.commit('setMarketOrder', { [id]: this.lines.get(id).getPrice() })
})
.onCancel(() => {
// Delete price line & set price = 0
this.deleteLine(id)
this.$store.commit('setMarketOrder', { [id]: 0 }) // set 0 value in vuex storage
})
},
line({ id = 'price', text = 'Price', color = '#ff9f0a', price, quantity, fontColor = '#fff', lineStyle = 2, lineLength = 25 }) {
if (this.lines.has(id)) this.deleteLine(id)
// Creating line from scratch
const widget = this.widget.chart().createOrderLine()
.setText(text)
.setPrice(price)
.setQuantity(quantity)
.onModify(res => res) // Need for dragging
// Customize color
.setLineColor(color)
.setBodyTextColor(fontColor)
.setBodyBorderColor(color)
.setBodyBackgroundColor(color)
.setQuantityBorderColor(color)
.setQuantityTextColor(fontColor)
.setQuantityBackgroundColor(color)
.setCancelButtonBorderColor(color)
.setCancelButtonBackgroundColor(color)
.setCancelButtonIconColor(fontColor)
.setLineLength(lineLength) // Margin right 25%
.setLineStyle(lineStyle)
this.lines.set(id, widget)
return widget // return for orderLine func()
},
deleteLine(id) {
this.lines.get(id).remove()
this.lines.delete(id)
},
deleteLines() {
this.lines.forEach((value, key) => this.deleteLine(key))
}
}
}
, . , .
PineScript
charting_library . PineScript JavaScript .
There is no such functionality in the free version of charting_library . If necessary, you can do it yourself HTML + CSS.
Open source
- tradingview-jsapi-binance - connected Binance exchange with JS API adapter and WebSocket stream
- tradingview-jsapi-forex - Forex data for JS API adapter. Minute data refresh without WebSocket with Save \ Load methods
Conclusion
The article will be supplemented. If there is a case with a problem - a solution, write, I will add an article indicating authorship.
It is also interesting to hear your opinion, experience, questions and wishes.
Thank you for attention!