Open another python file using tkinter button with parameters/entry - python

I'm trying to build a trading bot for binance. I've managed to build the bot and use it via terminal.
However, I want to make a GUI that runs the bot in the same way it would in terminal, except this time with an interface. I've rebuilt the bot to connect to the binance data-stream to test it. By using various "tickers" (eg. ethusdt, btcusdt), I can tune the bot to look at specific streams for price information.
The problem now is that I can start the program using the button (linked to bot.py), but I still have to enter the ticker manually in the terminal after it starts.
So my question is- how do I pass the ticker as a parameter to the program so that it starts automatically, and not have to enter it after? I basically want to enter a ticker in the entry field (ticker_entry) and pass it in to the bot.py ticker request.
Here's the code I've written for bot.py:
import websocket
import json
import pprint
from colorama import Fore, Back, Style
import numpy as np
import tkinter as tk
tmp = [1]
print("eg. ethusdt = ETH/USDT")
ticker = input(Fore.YELLOW + "Enter ticker here: ")
print(Style.RESET_ALL)
SOCKET="wss://stream.binance.com:9443/ws/"+ ticker +"#kline_1m"
def on_open(ws):
print('Connection established')
def on_close(ws):
print("Connection closed")
def on_message(ws, message):
global tmp
print("waiting for candle to close..")
json_message = json.loads(message)
candle = json_message['k']
is_candle_closed = candle['x']
close = candle['c']
if is_candle_closed:
print("\n")
print(Fore.RED + "Last candle closed at: ")
print(*tmp, sep= ' , ')
print(Style.RESET_ALL)
print("\n")
print(Fore.GREEN +"New candle closed at: \n{}".format(close))
print("\n")
print(Style.RESET_ALL)
tmp.pop(0)
tmp.append(float(close))
ws = websocket.WebSocketApp(SOCKET, on_open=on_open, on_close=on_close, on_message=on_message)
ws.run_forever()
And here is the code I've written with the tkinter module:
from tkinter import *
from tkinter import filedialog
import os
root = Tk()
WIDTH = 396
HEIGHT = 594
root.title('CheckBot')
root.geometry("396x594")
bg = PhotoImage(file = "rocket.png")
def start_bot(ticker_entry):
trader = 'bot.py'
os.system('"%s"' % trader)
#Creating canvas
my_canvas = Canvas(root, width=WIDTH, height=HEIGHT)
my_canvas.pack(fill="both", expand=True)
#Setting image in canvas
my_canvas.create_image(0,0, image=bg, anchor="nw")
#Adding a label
my_canvas.create_text(200,30, text="Enter the ticker to trade", font=("Bembo Bold Italic", 15), fill="#cc5200")
ticker_entry = Entry(my_canvas, bg='white')
ticker.pack(pady=50)
my_canvas.create_text(200,100, text="Enter amount to trade", font=("Bembo Bold Italic", 15), fill="#cc5200")
amount = Entry(my_canvas, bg='white')
amount.pack(pady=10)
trade_button = Button(my_canvas, text='Start trading', bg='#00b359', fg='#000099', command=lambda:start_bot(ticker))
trade_button.pack(pady=70)
root.mainloop()

Generally, it isn't the best practice to spawn a whole new CMD shell using os.system(), and it is better to put all the main functionality in a function, then import that function and call it.
Instead of writing this in the tkinter file:
def start_bot(ticker_entry):
trader = 'bot.py'
os.system('"%s"' % trader)
Take the logic of bot.py and put it into a single function like so:
def start_bot(ticker):
print("eg. ethusdt = ETH/USDT")
print(Style.RESET_ALL)
SOCKET="wss://stream.binance.com:9443/ws/"+ ticker +"#kline_1m"
ws = websocket.WebSocketApp(SOCKET, on_open=on_open, on_close=on_close, on_message=on_message)
ws.run_forever()
Then, back in the tkinter file, you can simply import the starting function with from bot import start_bot, and the logic should stay in tact.
However, if you don't want to change any major aspect of your code, and want to keep using os.system(), then there yet another solution to your problem (again, it is highly recommended to use the above solution, since you are not relying on a redundant os.system() call which will waste more CPU and RAM resources).
When you run a file, you can specify file arguments in the command line. These arguments get passed to the script, but they don't do anything if they are not used. However, in your case, since you want to access them, you can do it like so:
from sys import argv
# Get the first command line argument
first_argument = argv[1]
# The thing to notice here is that you start from the index 1, since 0 is the script name. (When you run a python script, you are calling the python.exe file, and the script name is the first argument)
How does this connect with your question? Well, since you are already running the bot.py file in the command line (using os.system()), you can add a command line argument to specify the ticker. Then, from bot.py, you can get that ticker value from sys.argv.
def start_bot(ticker_entry):
trader = 'bot.py'
os.system('"{}" "{}"'.format(trader, ticker_entry)) # Pass the ticker after the script name
And in the bot.py file...
from sys import argv
ticker = argv[1]

You can pass it as a command-line parameter.
import sys
ticker = ""
if len(sys.argv) > 1:
ticker = sys.argv[1]
Then, if ticker is not empty, just fill in the text box, and call start_bot yourself.

Related

How to make an on/off switch for a function in a python program?

So I have this python program for CSGO hacks that has esp, aimbot, wallhacks and more!
Lets take the triggerbot code for example...
#imports
import pymem
from pymem import process
import os
import sys
import time
import win32
from win32 import win32api
import win32process
import keyboard
import threading
from threading import Thread
import offsets
from offsets import *
def trigger():
while True:
localplayer = pm.read_int(client + dwLocalPlayer)
crosshairid = pm.read_int(localplayer + m_iCrosshairId)
getteam = pm.read_int(client + dwEntityList + (crosshairid - 1)*0x10)
localteam = pm.read_int(localplayer + m_iTeamNum)
crosshairteam = pm.read_int(getteam + m_iTeamNum)
if crosshairid>0 and crosshairid<32 and localteam != crosshairteam:
pm.write_int(client + dwForceAttack, 6)
Now I want to be able to give the user an option to turn it on and off via tkinter switch, but I am not able to figure out on how to make it turn off completely once it is turned on.
I have tried somethings which I don't want to as they were stupid and also I searched on google couldn't find much.
Please help!
Take a look at this example:
from tkinter import *
root = Tk()
def run():
global rep
if var.get() == 1:
print('Hey')
rep = root.after(1000,run) #run the function every 2 second, if checked.
else:
root.after_cancel(rep) #cancel if the checkbutton is unchecked.
def step():
print('This is being printed in between the other loop')
var = IntVar()
b1 = Checkbutton(root,text='Loop',command=run,variable=var)
b1.pack()
b2 = Button(root,text='Seperate function',command=step)
b2.pack()
root.mainloop()
after() method takes two arguments mainly:
ms - time to be run the function
func - the function to run after the given ms is finished.
after_cancel() method takes the variable name of after() only.
Perhaps also keep in mind, you can use root.update() and root.update_idletasks() with while loops, but its not efficient either.
Hope this helped you understand better, do let me know if any doubts or errors.
Cheers

Reading serial input and printing to Tkinter GUI

I'm trying to make a Tkinter based GUI for an Arduino printing sensors value and responding to user input.
The code I'm trying to use to eliminate while loops is this, which doesn't print any sensor information, the only output is "Trying.../dev/ttyACM0" followed by the tkinter window opening.
import serial
import time
from Tkinter import *
connected = False
write_to_file_path = "output.txt"
output_file = open(write_to_file_path, "w+")
locations=['/dev/ttyACM0','/dev/ttyACM1','/dev/ttyACM2','/dev/ttyACM3']
for device in locations:
try:
print "Trying...",device
ser = serial.Serial(device, 9600)
break
except:
print "Failed to connect on",device
## loop until the arduino tells us it is ready
while not connected:
serin = ser.read()
connected = True
time.sleep(0.1)
ser.flushInput()
time.sleep(1)
def serialRead():
if ser.inWaiting():
line = ser.readline()
data = line.decode("utf-8").split('\t')
print(data)
output_file.write(line)
root.after(1000, serialRead)
root = Tk()
root.title("Temperature Control")
serialRead()
root.mainloop()
This, on the other hand, works perfectly with the exception of not having a tkinter window. But it removes old input from the buffer and reads in the new input.
import serial
import time
connected = False
write_to_file_path = "output.txt"
output_file = open(write_to_file_path, "w+")
serial_port = '/dev/ttyACM0'
baud_rate = 9600
ser = serial.Serial(serial_port, baud_rate, timeout=5)
time.sleep(0.1)
ser.flushInput()
time.sleep(1)
while True:
if ser.inWaiting():
line = ser.readline()
data = line.decode("utf-8").split('\t') #ser.readline returns a binary, convert to string
print data[0] + '\t' + data[1]
output_file.write(line)
This was inspired by a different stackoverflow post from a while ago: Run an infinite loop in the backgroung in Tkinter
I've seen some example using threading but I don't know much about python nor threading so I'd really like to get it to work with root.after() if that's possible. I've also tried the example using root.after, all of which are pretty similar to the one I linked, and I couldn't get any of them working. Am I doing anything obviously wrong or in a way that's much more difficult than it needs to be? I would appreciate it greatly if someone would point me in the right direction.
I made a UI in TK for reading data from a GPS receiver and I had difficulties getting root.mainloop() to run, so instead I put a callback inside the TK app that ends up calling root.update() rather than mainloop.
The code looks something like this:
class App:
def __init__(self, master):
self.sats = tk.StringVar()
self.satsnum = tk.Label(self.frame, textvariable=self.sats, bg="blue")
self.satsnum.pack()
def update_display(self, master):
while source:
self.sats.set(n_sats)
if n_sats < 10:
satsbgcolor = 'red'
else:
satsbgcolor = 'green'
self.satsnum.configure(bg = satsbgcolor)
master.update()
time.sleep(1)
with serial_link.get_base_args_driver(args) as driver:
with Handler(Framer(driver.read, driver.write, verbose=True)) as source:
root = tk.Tk()
app = App(root)
app.update_display(root)
Note, the time.sleep(1) is necessary on MacOS as tk.update() will leak memory if the update is called too fast.

Python gui - passing input to script

I currently have a main python script (main.py) which reads input from a second script (input.py) which can be modified by a user. The user sets variables such as number of dimensions (ndim), number of points (npts) etc. in the second script and these are read into main.py using the following:
filename = sys.argv[-1]
m = __import__(filename)
ndim = m.ndim
npts1 = m.npts1
npts2_recorded = m.npts2_recorded
The script is executed by the following command:
python main.py input
I would like to replace input.py with a GUI. Tkinter seems a sensible place to start and I can see how to create a GUI to enable the user to set the various options that they would otherwise have set in input.py. However, I do not know how to pass this information to main.py from the GUI. Is there an equivalent to __import(filename)__ which can extract information from selections made by a user in the GUI, or is there another way of achieving the same effect.
A minimal (not) working example based on the answer below:
This code creates the file example.txt but the text given to block1 does not get written to the file.
from Tkinter import *
def saveCallback():
with open("example.txt",'w') as outfile:
outfile.write(block1.get())
def UserInput(status,name):
optionFrame = Frame(root)
optionLabel = Label(optionFrame)
optionLabel["text"] = name
optionLabel.pack(side=LEFT)
var = StringVar(root)
var.set(status)
w = Entry(optionFrame, textvariable= var)
w.pack(side = LEFT)
optionFrame.pack()
return w
if __name__ == '__main__':
root = Tk()
block1 = UserInput("", "Block size, dimension 1")
Save_input_button = Button(root, text = 'Save input options', command = saveCallback())
Save_input_button.pack()
root.mainloop()
Use a file for that, save selections in the GUI to a file(just like you did before with input.py) and then read the file.
So, in your main.py
Open the GUI
The preferences entered by to user to the file
Read the file as you did before.
The only drawback here is that you have to make sure in your main.py script that the GUI have been already closed. For that you can use the subprocess module, there are several function there you can use for block until the process returns or ends.
With this approach you just have to type:
python main.py
and somewhere inside main.py:
# The function call will wait for command to complete, then return the returncode attribute.
rcode = subprocess.call(['python', 'gui_input.py'])
Code sample to write the value of an Entry to a file.
import tkinter
top = tkinter.Tk()
def saveCallback():
with open("example.txt", 'w') as outfile:
outfile.write(e1.get())
e1 = tkinter.Entry(top)
b1 = tkinter.Button(top, text ="Save", command = saveCallback)
e1.pack(side=tkinter.LEFT)
b1.pack(side=tkinter.RIGHT)
top.mainloop()

How to get tk to display something before a function is finished

I have this code which downloads the files specified in an online list and whilst doing that, display a loading screen with a label telling of which file is being downloaded and an indeterminate progress bar showing to the user something is happening. The download works very well, but what doesn't work is tkinter, which doesn't work until the process is finished, only showing the "Download Finished" label at the end. How can I get tkinter to display a window before a function is finished?
I have already tried
Spliting it up into multiple functions
Adding a sleep function to see if slowing it down helps
To display this what I am talking about, I have replaced my original code with some examples. Does anyone know how to make tkinter update more actively (before the function has finished)?
#Imports
import urllib.request as ur
import os
from tkinter import *
import tkinter.ttk as ttk
import time as T
#Globals
tk = None
where = None
progressbar = None
progresstitle = None
progressinfo = None
transfer = None
#Make sure that the download directory exists
def checkdir(filename):
directory = os.path.dirname(filename)
try:
os.stat(directory)
except:
os.mkdir(directory)
#First part (read q to see why I split up)
def part1():
#Get Globals
global tk
global where
global progressbar
global progresstitle
global progressinfo
global transfer
#Create Window
tk = Tk()
tk.title("Downloading...")
#Find out the location of the online files to download by reading the online txt file which contains their locations
where = str(ur.urlopen("http://example.com/whatfilestodownload.txt").read())
where = where[2:(len(where)-1)]
where = where.split(";")
#Create the progress bar
progressbar = ttk.Progressbar(tk, orient=HORIZONTAL, length=200, mode='indeterminate')
progressbar.grid(row = 2, column = 0)
#Create the labels
progresstitle = Label(tk, text = "Downloading Files!", font = ("Helvetica", 14))
progresstitle.grid(row = 0, column = 0)
progressinfo = Label(tk, text = "Starting Download...", font = ("Helvetica", 10))
progressinfo.grid(row = 1, column = 0)
#Engage Part Two
part2()
#Part Two
def part2():
#Get Globals
global tk
global where
global progressbar
global progresstitle
global progressinfo
global transfer
#Repeat as many times as files described in the only file describing .txt
for x in where
#The online file contains "onlinelocation:offlinelocation" This splits them up
x1 = x.split(":")[0]
x2 = x.split(":")[1]
#Read the online file and update labels
transfer = None
progressinfo.config(text = str("Downloading " + x2 + "..."))
transfer = str(ur.urlopen("http://example.com/" + x1).read())
progressinfo.config(text = str("Configuring downloaded file..."))
transfer = transfer [2:(len(transfer)-1)]
#Fix python turning "\n" to "\\n" by reversing
transfer = transfer.split("\\n")
transtemp = ""
for x in transfer:
transtemp = transtemp + "\n" + x
transfer = transtemp[1:len(transtemp)]
progressinfo.config(text = str("Installing " + x2 + "..."))
tw = None
checkdir(str(os.getcwd()+"/Downladed/"+x2))
tw = open(str(os.getcwd()+"/Downloaded/"+x2), "w")
tw.write(transfer)
tw.close()
#See if waiting helps
T.sleep(0.5)
part3()
def part3():
#Get Globals
global tk
global where
global progressbar
global progresstitle
global progressinfo
global transfer
#Final Screen
progressbar.grid_remove()
progressinfo.grid_remove()
progresstitle.config(text="You have downloaded\n the required files!")
progressbar.stop()
part1()
If updating the display at the end of each file being downloaded in your part2() function is enough, you can use the update_idletasks() method, putting it in place of T.sleep(), which will allow the GUI to refresh between going back to another iteration of your for loop.
Ref: http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.update_idletasks-method
This is why Tcl has asynchronous I/O functions. You need to keep processing windowing system events in a timely manner so you cannot wait for a complete file to download. Instead you need to do it in pieces. In Tcl we would use the fileevent command to set a procedure to be called each time some input became available from the socket. The rest of the time we can process other events. In Python the common way to do this is the Twisted package. This allows you to register event sources with twisted and make the entire application event oriented. You could also use threads and do the downloading on worker threads but that doesn't really help you with the progress notifications. There is some special handling to link up Tkinter and Twisted - see the documentation.

Urwid: how to see errors?

I am building application with interactive console interface (line htop, atop utilities) using urwid library, so my trouble is: as interface takes all the space in console window - I could not see python's errors, I tried to do that:
import sys
f = open("test_err", "w")
original_stderr = sys.stderr
sys.stderr = f
print a #a is undefined
sys.stderr = original_stderr
f.close()
It works when I dont use urwid, but not when I use it...
you could try redirecting errors to a file. after each time you run the program, you will need to refresh the file. most editors let you easily do this by pushing f5
def main():
#your code here
print someError #raises an error
try: #run main function
main()
except BaseException as err: #catch all errors
with open('errors.txt','a') as errors: #open a file to write the errors to
errors.write(err.message+'\n')#write the error
change the 'a' to 'w' in the open function if you only want to see one error in the file at a time (instead of having multiple error over a long period of time in one file).
if you want to see the error right when it happens, you can make the error catcher open a window that has the error on it.
def main():
#your code here
print someErr
try: #run main function
main()
except BaseException as err: #catch all errors
import Tkinter as tk #imports the ui module
root = tk.Tk() #creates the root of the window
#creates the text and attaches it to the root
window = tk.Label(root, text=err.message)
window.pack()
#runs the window
root.mainloop()
if you want to build your own window to catch errors, you can learn about Tkinter here. (it is built into python, you don't have to install anything)
Here's what I came up with. I'm taking advantage of unicode-rxvt (urxvt) feature to be passed in a file descriptor. Of course this means you need to be developing this in an X environment, and not a console.
from __future__ import print_function
import os
from datetime import datetime
_debugfile = None
def _close_debug(fo):
fo.close()
def DEBUG(*obj):
"""Open a terminal emulator and write messages to it for debugging."""
global _debugfile
if _debugfile is None:
import atexit
masterfd, slavefd = os.openpty()
pid = os.fork()
if pid: # parent
os.close(masterfd)
_debugfile = os.fdopen(slavefd, "w+", 0)
atexit.register(_close_debug, _debugfile)
else: # child
os.close(slavefd)
os.execlp("urxvt", "urxvt", "-pty-fd", str(masterfd))
print(datetime.now(), ":", ", ".join(map(repr, obj)), file=_debugfile)
This will open a new terminal window automatically when you call DEBUG for the first time and close it at exit. Then any messages passed to it are shown in this new window. This is your "debug window". So your main app works normally, without cluttering it up with messages, but you can still see debug output in this new terminal.

Categories