主要分成selling_price.py(主程式)跟selling_price_gui.py(GUI)兩隻,
Mac執行:
python3 selling_price_gui.py
python3 selling_price.py 買入價格
Windows執行:
py -3 selling_price_gui.py
py -3 selling_price.py 買入價格
主要遇到的問題應該是GUI輸入浮點數跟主程式處理浮點數的問題,
主程式用round函式取近似值抓精度來解決,GUI則一輸入就丟去檢查
GUI的code是參考程式語言教學誌跟良葛格的python專欄:
http://www.kaiching.org/2014/07/python-guide.html
https://openhome.cc/Gossip/CodeData/PythonTutorial/index.html
selling_price.py 程式碼&註解:
# -*- coding:utf-8 -*-
# file: selling_price.py
#
class SellingPrice():
# 常數定義
# TAX: 證券交易稅 3/1000
# CHARGE: 手續費 1.425/1000
TAX = 0.003
CHARGE = 0.001425
# ticks_table 欄位定義
# MINIMUM: tick range 最小邊界
# MAXIMUM: tick range 最大邊界
# TICK: 這個 range 的 tick 大小
MINIMUM = 0
MAXIMUM = 1
TICK = 2
# 使用 round 函式解決浮點數精確度的問題
# 取到小數點後兩位數
ACCURATE = 2
# 不同的股價區間有不同的 tick 數值
ticks_table = [
[0, 10, 0.01],
[10, 50, 0.05],
[50, 100, 0.10],
[100, 500, 0.50],
[500, 1000, 1],
[1000, 0, 5]
]
# 藉由輸入的數值 cost (購買一張1000股的價格)
# 去 ticks_table 裡面找到對應的 tick 值
def get_tick(self, cost):
for ticks in self.ticks_table:
if cost < ticks[self.MAXIMUM]:
return ticks[self.TICK]
return ticks[self.TICK]
# 藉由輸入的數值 cost (購買一張1000股的價格)
# 算出所需的購入金額 ($NT dollars)
def calculate_cost_nt(self, cost):
return int(cost * 1000 * (1 + self.CHARGE))
# 藉由輸入的數值 sell_price (賣出一張1000股的價格)
# 算出最終賣出的金額 ($NT dollars)
def calculate_sell_nt(self, sell_price):
return int(sell_price * 1000) - int(sell_price * 1000 * (self.CHARGE + self.TAX))
# 檢查買入賣出的金額不能為 0
# 另外也根據買入賣出價格所處的 range 來抓出 tick
# 在那個 range 中的最小移動單位應該要為 tick
# 假如沒辦法被 tick 除盡,代表是不正確的輸入值
def input_check(self, cost, tick):
if cost <= 0:
return False
if round(cost * 100, self.ACCURATE) % round(tick * 100, self.ACCURATE) != 0:
return False
return True
# 先檢查輸入值是否合法,再算出損益
# (賣出價格 - 購入成本) = 實際損益
def calculate_gain_loss(self, cost, sell):
if (self.input_check(cost, self.get_tick(cost)) == False) | \
(self.input_check(sell, self.get_tick(sell)) == False):
return 0
return (self.calculate_sell_nt(sell) - self.calculate_cost_nt(cost))
# 先檢查輸入值,算出購入成本
# 再找出滿足條件的 n: "買入價格 + (n * tick)" >= "購入成本"
# 根據 n 算出 "至少不虧本的賣價 = 買入價格 + (n * tick)"
def calculate_least_price(self, cost):
tick = self.get_tick(cost)
if self.input_check(cost, tick) == False:
return 0
cost_nt = self.calculate_cost_nt(cost)
sell_price = round(cost + tick, self.ACCURATE)
while True:
sell_nt = self.calculate_sell_nt(sell_price)
if sell_nt >= cost_nt:
return sell_price
sell_price = round(sell_price + tick, self.ACCURATE)
import sys
if __name__ == '__main__':
Selling = SellingPrice()
sell_price = Selling.calculate_least_price(float(sys.argv[1]))
if sell_price == 0:
print("Input valure error!")
else:
print(sell_price)
selling_price_gui.py 程式碼&註解:
# -*- coding:utf-8 -*-
# file: selling_price_gui.py
#
import tkinter
from selling_price import SellingPrice
class SellingPriceGUI(tkinter.Frame):
ACTION_INSERT = '1'
# input 最大輸入的長度
INPUT_MAX_LENGTH = 10
# 設定函式 check_imput 檢查的類型,可以是 int 或 float
check_type = float
Selling = SellingPrice()
def __init__(self, master=None):
tkinter.Frame.__init__(self, master)
# tkinter 有 pack、grid、place三種佈局管理
# grid(row=列, column=行, rowspan=佔用列數, columnspan=佔用行數)
self.grid()
self.create_widgets()
def create_widgets(self):
# (action)'%d', (index)'%i', (value_if_allowed)'%P'
# 代表傳入 check_imput 的三個參數的型態
validate_command = (self.register(self.check_imput), '%d', '%i', '%P')
self.buy_lable = tkinter.Label(self)
self.buy_lable["text"] = "買入價格:"
self.buy_lable.grid(row=0, column=0)
self.buy_entry = tkinter.Entry(self, validate = 'key', validatecommand = validate_command)
self.buy_entry["width"] = 20
self.buy_entry.grid(row=0, column=1)
self.sell_lable = tkinter.Label(self)
self.sell_lable["text"] = "賣出價格:"
self.sell_lable.grid(row=1, column=0)
self.sell_entry = tkinter.Entry(self, validate = 'key', validatecommand = validate_command)
self.sell_entry["width"] = 20
self.sell_entry.grid(row=1, column=1)
self.least_lable = tkinter.Label(self)
self.least_lable["text"] = "不虧損價:"
self.least_lable.grid(row=2, column=0)
self.least_sell_result = tkinter.Label(self)
self.least_sell_result["text"] = ""
self.least_sell_result.grid(row=2, column=1)
self.profit_lable = tkinter.Label(self)
self.profit_lable["text"] = "實際盈虧:"
self.profit_lable.grid(row=3, column=0)
self.profit_loss_result = tkinter.Label(self)
self.profit_loss_result["text"] = ""
self.profit_loss_result.grid(row=3, column=1)
self.clear_button = tkinter.Button(self)
self.clear_button["text"] = "清除"
self.clear_button.grid(row=4, column=0)
self.clear_button["command"] = self.clear
self.count_button = tkinter.Button(self)
self.count_button["text"] = "計算"
self.count_button["width"] = 20
self.count_button.grid(row=4, column=1)
self.count_button["command"] = self.calculate
# 限制輸入的長度,以及內容必須是整數或是浮點數
# 檢查的類型則是由 self.check_type 決定
def check_imput(self, action, index, value_if_allowed):
if action != self.ACTION_INSERT:
return True
if int(index) > self.INPUT_MAX_LENGTH:
return False
try:
return self.check_type(value_if_allowed)
except ValueError:
return False
def calculate(self):
# 計算最低不虧損價格
cost_input = self.buy_entry.get()
if cost_input != "":
least_sell_price = self.Selling.calculate_least_price(float(cost_input))
if least_sell_price == 0:
self.least_sell_result["text"] = "輸入錯誤!"
else:
self.least_sell_result["text"] = least_sell_price
# 計算實際損益
sell_input = self.sell_entry.get()
if sell_input != "":
gain_loss = self.Selling.calculate_gain_loss(float(cost_input), float(sell_input))
if gain_loss == 0:
self.profit_loss_result["text"] = "輸入錯誤!"
else:
self.profit_loss_result["text"] = str(gain_loss) + "元"
# 清除輸入輸出值
def clear(self):
self.buy_entry.delete(0, self.buy_entry["width"])
self.sell_entry.delete(0, self.sell_entry["width"])
self.least_sell_result["text"] = ""
self.profit_loss_result["text"] = ""
if __name__ == '__main__':
root = tkinter.Tk()
root.title("賣價計算")
app = SellingPriceGUI(master=root)
app.mainloop()