Skip to content

Instantly share code, notes, and snippets.

@trumad
Last active September 11, 2023 12:10
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trumad/75a4013ace429c3854e967d6bbe1c6fc to your computer and use it in GitHub Desktop.
Save trumad/75a4013ace429c3854e967d6bbe1c6fc to your computer and use it in GitHub Desktop.
See multiple tradingview windows side by side, hopefully without ads and popups.
# First install webview:
# pip install pywebview
# Then create a config.json file in the same folder with the following options:
"""
{
"content":[
{
"exchange":"Bitstamp",
"ticker":"BTCUSD",
"interval": 1440
},
{
"exchange":"Binance",
"ticker":"ETHUSD"
},
{
"exchange":"Binance",
"ticker":"ADAUSD"
},
{
"exchange":"Binance",
"ticker":"DOTUSD"
}
],
"screen-width":3840,
"screen-height":2180,
"no_of_columns":2,
"interval": 60
}
"""
# Set your max screen width and height (Or use trial & error!)
# Set your exchanges and ticker pairs
# Decide how many colums you want to divide the screen into
# Decide whether you want a longer/shorter interval. Only set to something a free account can access.
# Then run:
# python tradingViewMultipleWindows.py
import json
import math
import webview
#import pyautogui
javascript = r"""
function sleep(ms) { // usage: await sleep(4000)
return new Promise(resolve => setTimeout(resolve, ms));
}
//Check the DOM for changes and run a callback function on each mutation
function observeDOM(callback) {
var mutationObserver = new MutationObserver(function (mutations) { //https://davidwalsh.name/mutationobserver-api
mutations.forEach(function (mutation) {
//console.log(mutation)
callback(mutation) // run the user-supplied callback function,
});
});
// Keep an eye on the DOM for changes
mutationObserver.observe(document.body, { //https://blog.sessionstack.com/how-javascript-works-tracking-changes-in-the-dom-using-mutationobserver-86adc7446401
attributes: true,
// characterData: true,
childList: true,
subtree: true,
// attributeOldValue: true,
// characterDataOldValue: true,
// attributeFilter: ["class"] // We're really only interested in stuff that has a className
});
}
observeDOM(doDomStuff);
cleanUpPage();
async function cleanUpPage(){
while (true){
await sleep(15000);
closeBackDrop(document);
removeGoogleAds(document);
}
}
function doDomStuff(mutation){
if (!mutation.target.className) { return }
if (!mutation.target.className === "") { return }
if (mutation.target.className.includes("chart-page")) { // basically ends up being the whole page, sometimes.
// closeBackDrop(mutation.target);
// removeGoogleAds(mutation.target);
}
if (mutation.target.className.includes("toast-")) {
// removeGoogleAds(mutation.target);
}
if (mutation.target.className.includes("layout__area--top")) { // the bar at the top of the screen
removeStartFreeTrialButton(mutation.target);
}
if (mutation.target.className.includes("toast-positioning-wrapper")) { // this fires when a popup happens
// console.log(mutation.target);
removePopups(mutation.target);
}
/* if (mutation.target.textContent.includes("Take your trading to the next level")){
console.log(mutation.target);
}*/
/* if (mutation.target.querySelector("iframe[id*=google_ads_iframe]")){
console.log(mutation.target);
}*/
if (mutation.target.className.includes("layout__area--center")){
// console.log(mutation.target);
removePressAndHoldNotification(mutation.target);
}
}
function removeGoogleAds(el){
if (document.querySelector("iframe[id*=google_osd_static_frame]")){
document.querySelector("iframe[id*=google_osd_static_frame]").remove();
}
if (!document.querySelector("iframe[id*=google_ads_iframe]")){return}
var frame = document.querySelector("iframe[id*=google_ads_iframe]");
// console.log(frame);
var closestWrapper = frame.closest("[class*=toast-positioning-wrapper-]");
if (!closestWrapper){return}
var closeButton = closestWrapper.querySelector("[class*=close-button-]");
if (!closeButton){return}
closeButton.click();
console.log("BYE GOOGLE!");
// document.querySelector("[id*=div-gpt-ad]").closest("[class*=toast-positioning-wrapper-]").querySelector("[class*=close-button-]").click()
}
function removeStartFreeTrialButton(el){
if (!el.textContent.includes("Start free trial")){return}
if (!el.querySelector("#header-toolbar-start-trial")){return}
el.querySelector("#header-toolbar-start-trial").parentElement.remove();
}
function removePressAndHoldNotification(el){
if (!el.textContent.includes("Press and hold")){return}
if (!el.querySelector("[class*=container-] > [class*=closeButton]")){return}
el.querySelector("[class*=container-] > [class*=closeButton]").click();
}
function removePopups(el){
if (el.textContent.includes("This website uses cookies")){
if (!el.querySelector("button")){return};
el.querySelector("button").click(); //accept cookies
}
if (el.textContent.includes("Unlock the full power of TradingView")){ // free trial ad
if (!el.querySelector("[class*=close-icon-]")){return};
el.querySelector("[class*=close-icon-]").click(); // close
}
}
function closeBackDrop(el){
if (!el.querySelector("[class*=backdrop]")){return}
var backDrop = el.querySelector("[class*=backdrop]"); // opaque div that covers the whole page
if (!backDrop.nextElementSibling){return}
var wrap = backDrop.nextElementSibling; // "sign up" notification
wrap.remove(); // remove sign up notiication
backDrop.remove(); // remove opaque backdrop
}
"""
def windows():
""""""
origWindow.resize(200,50)
origWindow.move(0,0)
config_details = get_config_details()
total_width = config_details['screen-width']
total_height = config_details['screen-height']
no_of_windows = len(config_details['content'])
print("Current window setup = {}x{}".format(total_width, total_height))
#print("No. of windows to create: {}".format(no_of_windows))
no_of_columns_to_make = config_details['no_of_columns']
print("No. of columns to make: {}".format(no_of_columns_to_make))
x_position = 0
y_position = 0
if no_of_windows <= no_of_columns_to_make:
no_of_rows_to_make = 1
else:
no_of_rows_to_make = math.ceil(no_of_windows / 2)
print("No of rows to make: {}".format(no_of_rows_to_make))
#print("no of rows: {}".format(no_of_rows_to_make))
column = 1
row = 1
current_grid = 1
for each in config_details['content']:
#print(each)
if 'interval' in config_details:
the_url = 'https://uk.tradingview.com/chart/?symbol={}%3A{}&interval={}'.format(each['exchange'], each['ticker'], config_details['interval'])
else:
the_url = 'https://uk.tradingview.com/chart/?symbol={}%3A{}'.format(each['exchange'], each['ticker'])
#print(the_url)
win = webview.create_window("Tradingview - {} - Kill the terminal running the python to close".format(each['ticker']), the_url, frameless=False)
win.evaluate_js(javascript)
win.resize(total_width/no_of_columns_to_make, total_height/no_of_rows_to_make)
# placement of windows
#print("current_grid: {}".format(current_grid))
win.move(x_position, y_position)
#print("Current row= {}".format(row))
#print("Current col= {}".format(column))
if column == no_of_columns_to_make:
row += 1
column = 1
else:
column += 1
#print(row, column)
x_position = (total_width / no_of_columns_to_make) * (column - 1)
y_position = (total_height / no_of_rows_to_make) * (row - 1)
current_grid += 1
#print("new x: {}".format(x_position))
#print("new y: {}".format(y_position))
origWindow.destroy()
def get_config_details():
""""""
with open('config.json') as the_file:
data = json.load(the_file)
return data
if __name__ == '__main__':
# Master window
origWindow = webview.create_window('Launching', html='<h1>Launching...</h1>', frameless=True)
# win2 = webview.create_window('BTC/GBP', 'https://uk.tradingview.com/chart/?symbol=BITSTAMP%3ABTCUSD', frameless=True)
webview.start(windows)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment