<style> html, body, .ui-content { background-color: #333; color: #ddd; } body > .ui-infobar { display: none; } .ui-view-area > .ui-infobar { display: block; } .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { color: #ddd; } .markdown-body h1, .markdown-body h2 { border-bottom-color: #ffffff69; } .markdown-body h1 .octicon-link, .markdown-body h2 .octicon-link, .markdown-body h3 .octicon-link, .markdown-body h4 .octicon-link, .markdown-body h5 .octicon-link, .markdown-body h6 .octicon-link { color: #fff; } .markdown-body img { background-color: transparent; } .ui-toc-dropdown .nav>.active:focus>a, .ui-toc-dropdown .nav>.active:hover>a, .ui-toc-dropdown .nav>.active>a { color: white; border-left: 2px solid white; } .expand-toggle:hover, .expand-toggle:focus, .back-to-top:hover, .back-to-top:focus, .go-to-bottom:hover, .go-to-bottom:focus { color: white; } .ui-toc-dropdown { background-color: #333; } .ui-toc-label.btn { background-color: #191919; color: white; } .ui-toc-dropdown .nav>li>a:focus, .ui-toc-dropdown .nav>li>a:hover { color: white; border-left: 1px solid white; } .markdown-body blockquote { color: #bcbcbc; } .markdown-body table tr { background-color: #5f5f5f; } .markdown-body table tr:nth-child(2n) { background-color: #4f4f4f; } .markdown-body code, .markdown-body tt { color: #eee; background-color: rgba(230, 230, 230, 0.36); } a, .open-files-container li.selected a { color: #5EB7E0; } </style>} # Python + Flask 虛擬美國股票交易網站 Part8 (Watchlist/ 顯示持股清單/ 查詢交易紀錄) ###### tags: `CS50` `Python` `Flask` ## 前言 前面已經把基本的交易股票功能做好了,再來就是要在網頁顯示現在所持有的個股,以及如果有需要,可以到個人交易頁面查詢交易紀錄。 ## 新增至觀察清單 新增到觀察清單的途徑是先使用quote功能查詢股票資訊,再從股票個資的頁面按加入觀察清單來新增。因為一樣需要登入才能使用此功能,所以也是將這個程式碼放在`admin.py` 裡。 ``` @admin_bp.route("/watchlist/new", methods=['GET', 'POST']) @login_required def add_to_watchlist(): symbol = request.args.get("symbol") symbol_in_watchlist = Watchlist.query.filter_by(symbol = symbol) for stock in symbol_in_watchlist: if stock.symbol == symbol: flash("Already existed in your watchlist",'success') return redirect(url_for('admin.show_watchlist')) watchlist = Watchlist(symbol = symbol) db.session.add(watchlist) current_user.watchlist.append(watchlist) db.session.commit() return redirect(url_for('admin.show_watchlist')) ``` 接著是顯示觀察清單的頁面 vfinance/templates/admin/watchlist.html ``` {% extends 'base.html' %} {% from 'bootstrap/form.html' import render_form %} {% block title %}WatchList{% endblock %} {% block content %} <div class="page-header"> <h1>Wathchlist <small class='text-muted'>{{ pagination.total }}</small> </h1> {% if watchlist %} <table class='table table-striped'> <thead> <th>Symbol</th> <th>Price</th> <th>Change</th> <th>ChangePercent</th> <th>Open</th> <th>High</th> <th>Low</th> <th>Volume</th> <th>Week52High</th> <th>Week52Low </th> </thead> {% for n in range(pagination.total) %} <tr> <td><a href="{{ url_for('admin.show_quote', symbol = symbols[n])}}">{{ symbols[n] }}</a></td> <td>{{ symbols[n]}}</td> <td>{{ changes[n] }}</td> <td>{{ changePercents[n] }}</td> <td>{{ openprices[n] }}</td> <td>{{ highs[n] }}</td> <td>{{ lows[n] }}</td> <td>{{ volumes[n] }}</td> <td>{{ week52Highs[n] }}</td> <td>{{ week52Lows[n] }} </td> <td> <form class="inline" method="post" action="{{ url_for('admin.delete_from_watchlist', symbol = symbols[n], next=request.full_path) }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure?');">Delete </button> </form> </td> </tr> {% endfor %} </table> {% endif %} {% endblock %} {% block footer %}{% endblock %} ``` 在觀察清單中,每隻個股的最後一個欄位放了一個delete按鈕,只要當用戶對這個股票心灰意冷的時候,就可以按下去。 ``` <form class="inline" method="post" action="{{ url_for('admin.delete_from_watchlist', symbol = symbols[n], next=request.full_path) }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure?');">Delete</button> </form> ``` ## 用戶首頁 從資料庫查找用戶的portfolio是否有值,有的話就會渲染出來。並簡單的計算一下現在的持股水位 ``` from flask import render_template, flash, redirect, url_for, request, current_app, Blueprint, abort, make_response from flask_login import current_user from vfinance.forms import QuoteForm from vfinance.models import TradeHistory, Portfolio, User from vfinance.utils import lookup from vfinance.extensions import db home_bp = Blueprint("home", __name__) @home_bp.route("/") def index(): if current_user.is_authenticated: form = QuoteForm() if form.validate_on_submit(): symbol = form.symbol.data return redirect(url_for('admin.show_quote', symbol = symbol)) cash = current_user.cash page = request.args.get('page',1,type=int) per_page = 15 pagination = Portfolio.query.with_parent(current_user).order_by(Portfolio.name.asc()).paginate(page,per_page) portfolio = pagination.items prices = [] position = 0 if portfolio: for stock in portfolio: quote = lookup(stock.symbol) quantity = stock.quantity price = quote['price'] prices.append(price) position += float(quantity)*float(price) current_user.position = position account_value = float(cash) + float(position) db.session.commit() return render_template("home/index.html", portfolio= portfolio, pagination = pagination, prices = prices, cash = cash, position = position, account_value = account_value, form = form) else: return render_template("home/index.html") ``` ``` {% extends "base.html" %} {% from 'bootstrap/pagination.html' import render_pagination %} {% block title %} Home {% endblock title %} {% block content %} {% if current_user.is_authenticated %} <div class="page-header"> <h1>Welcome: {{ current_user.username }}</h1> <h3>Account Value: {{ account_value }}</h3> <h3>Buying power: {{ cash }}</h3> <h3>Position: {{ position }}</h3> </div> <div class='row'> <div class='col-sm-10'> {% if portfolio %} <h3> Portfolio<small class='text-muted'>{{ pagination.total }}</small> </h3> <table class="table table-striped"> <thead> <tr> <th>Symbol</th> <th>Qty</th> <th>Pruch</th> <th>Mkt Price</th> </tr> </thead> {% for n in range(pagination.total) %} <tr> <td><a href="{{ url_for('admin.show_quote', symbol = portfolio[n].symbol)}}">{{ portfolio[n].symbol}}</a></td> <td>{{ portfolio[n].quantity}}</td> <td>{{ portfolio[n].purchase_price}}</td> <td>{{lookup(portfolio[n].symbol)['price']}}</td> </tr> {% endfor %} </table> {% endif %} <div class="page-footer">{{ render_pagination(pagination) }}</div> </div> <div class='col-sm-2 sidebar'> {% include "home/_sidebar.html" %} </div> </div> {% endif %} {% endblock content %} ``` 在用戶首頁的模板中,插入了_sidebar.html 主要是能讓用戶在首頁也看到自己觀察清單的價位 vfinance/templates/home.html ``` {% if watchlist %} <div class='row'> <div class="card mb-6"> <div class="card-header">Watchlist</div> <ul class="list-group list-group-flush"> {% for n in range(watchlist|length) %} <li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"> <a href="{{ url_for('admin.show_quote', symbol = watchlist[n].symbol)}}"> {{ watchlist[n].symbol}} </a> &nbsp;&nbsp;&nbsp;&nbsp; <a> {{ priceInWatchlist[n] }} </a> </li> {% endfor %} </ul> </div> </div> {% endif %} ``` ## 交易明細 每次交易的時候都會紀錄交易明細,現在只要寫個function把交易明細從資料庫拿出來然後渲染到一個html上面就好了 ``` @admin_bp.route("/trade_history", methods=['GET', 'POST']) @login_required def trade_history(): # get data from database page = request.args.get('page', 1, type=int) per_page = 10 pagination = TradeHistory.query.with_parent(current_user).order_by(TradeHistory.timestamp.desc()).paginate(page, per_page) trade_history = pagination.items return render_template("admin/trade_history.html", trade_history = trade_history, pagination = pagination) ``` vfinance/templates/admin/trade_histoy.html ``` {% extends 'base.html' %} {% from 'bootstrap/form.html' import render_form %} {% block title %}Login{% endblock %} {% block content %} <div class="page-header"> <h1>Trade History <small class="text-muted">{{ pagination.total }}</small> </h1> </div> {% if trade_history %} <table class= 'table table-striped'> <thead> <tr> <th>TimeStamp</th> <th>Transaction</th> <th>Change</th> </tr> </thead> {% for trade in trade_history %} <tr> <td>{{trade.timestamp}}</td> <td>{{ trade.symbol }} {{ trade.action }} {{ trade.quantity }} @ {{ trade.price }}</td> <td>{% if trade.action =="Buy" %} {{ (trade.quantity)*(trade.price)*(-1)}} {% else %} {{ (trade.quantity)*(trade.price)}} {% endif %}</td> </tr> {% endfor %} </table> {% endif %} {% endblock %} {% block footer %}{% endblock %} ``` 這樣基本上就完成了這個虛擬股票交易網站,雖然頁面有點醜就是了。