---
# System prepended metadata

title: Python + Flask 虛擬美國股票交易網站 Part7 （執行交易）
tags: [Flask, CS50, Python]

---

<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 虛擬美國股票交易網站 Part7 （執行交易）
###### tags: `CS50` `Python` `Flask`
## 前言

要交易一支股票通常會經過以下幾個步驟
* 查價
* 選擇（買/賣) 以及股數
* 確認交易明細
* 送出交易單

這些function都會寫在 vfinance/bluepirnts/admin.py 內

## 查價
```
@admin_bp.route('/quote', methods=["GET", "POST"])
@login_required
def get_quote():
    form = QuoteForm()
    if form.validate_on_submit():
        symbol = form.symbol.data
        
        quote = lookup(symbol)
        
        if quote:
            
            return redirect(url_for(".show_quote", symbol = symbol))
        else:
            flash("Cannot find", 'warning')
            return redirect_back()
    return render_template("admin/quote.html", form = form)
```
```
<form class="form-inline my-2 my-lg-0" method = 'post' action="{{ url_for('admin.get_quote')}}">
    {% if form %}
        {{form.csrf_token}}    
    {% endif %}
    <input class="form-control mr-sm-2" type="search" placeholder="Search"  aria-label="Search" id ='symbol' name = 'symbol'>
    <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Quote</button>
</form>
```
原本是有做一個查價的頁面，後來覺得直接做在navbar上面比較簡單直觀，不論在哪個頁面想查價都可以直接查，所以就寫在了基模板上面。因為是用form的形式，所以一定要加一個csrf_token，不然會出現報錯；模板使用`action="{{ url_for('admin.get_quote')}}"` 傳回資訊到 `get_quote`函數，對應的視圖函數 `get_quote()` 使用`form.symbol.data` 接收到參數後會接到上一篇說的 `lookup()` 查價功能，如果有查到就會導向下一個`show_quote` 函數，如果沒有就會重新導向現在的頁面，並顯示'Cannot find'。

## 顯示個股資訊
```
@admin_bp.route('/quote/<string:symbol>', methods =["GET","POST"])
@login_required
def show_quote(symbol):
    form = QuoteForm()
    quote = lookup(symbol)
    company = quote["name"]
    price = quote["price"]
    symbol = quote['symbol']
    
   
    return render_template("admin/show_quote.html",form = form, company = company, price = price, symbol = symbol)
```
#### vfinance/templates/admon/show_quotes
```
{% extends 'base.html' %}
{% from 'bootstrap/form.html' import render_form %}

{% block title %}Login{% endblock %}

{% block content %}
    <div class="container h-100">
        
        <div>
            <h1>Search for: {{symbol}}</h1>
            <h1>Company: {{ company }}</h1>
            <h1>Price: {{ price }}</h1>
            <h1>
                {% if symbol %}
                <a href = "{{ url_for('admin.get_trade', symbol = symbol) }}">
                    Trade {{symbol}}
                </a>
                <a href="{{url_for('admin.add_to_watchlist', symbol = symbol)}}">+Watchlist</a>
                {% endif %}
            </h1> 
    </div>
{% endblock %}

{% block footer %}{% endblock %}

```
show_quote是用get的方式接收到參數(symbol)，接受到之後會馬上再去執行lookup查詢個股資訊，並傳回所需要的值，最後渲染到對應的模板`show_quote.html` 上。
在`show_quote.html` 裡除了顯示個股相關數據之外，還有兩個連結，一個是要連到交易的頁面 `get_trade`，一個是要把這個個股加入觀察清單 `add_to_watchlist` 。

## 交易頁面

```

@admin_bp.route("/trade/<string:symbol>", methods=['GET', 'POST'])
@login_required
def get_trade(symbol):
    
    form = TradeForm()
    quote = lookup(symbol)
    if form.validate_on_submit():
        symbol = form.symbol.data
        quantity = form.quantity.data
        price = form.price.data
        action = form.action.data
        return redirect(url_for('.review_order', symbol = symbol, quantity= quantity, price = price, action = action))
    form.symbol.data = symbol
    form.price.data = quote['price']
    return render_template("admin/get_trade.html", form = form)

```
vfinance/templates/admin/get_trade:
```
{% extends 'base.html' %}
{% from 'bootstrap/form.html' import render_form %}

{% block title %}Login{% endblock %}

{% block content %}
   
    <div class="row h-100 page-header justify-content-center align-items-center">
        <h1>Trade Center</h1>
    </div>
    <div class="row h-100 justify-content-center align-items-center">
        {{ render_form(form, extra_classes='col-6') }}
    </div>
</div>
    
{% endblock %}

{% block footer %}{% endblock %}

```
進到交易頁面會先用bootstrap的 `render_form` 功能渲染之前做的tradeForm，在tradeForm的symbol以及price欄位中，用`form.symbol.data = symbol` 以及 `form.price.date = quote['price']` 先放在裡面，剩下的quantity 以及 action 讓用戶自己選擇股數以及是要買還是賣。

## 檢視交易明細頁面

在交易頁面選定好要買或賣一隻個股以及所需的股數後，按下送出，就會自動導向到review_order頁面。
但在檢視交易明細頁面中有幾個地方需要檢查
* 買股票 `if action == 'Buy'`
買股票比較單純，只要檢查帳戶的錢夠不夠交易就好了，因此會先藉由查詢用戶有多少現金 `current_user.cash`以及總購買股票的價格來做比較，如果錢不夠就會導向前一個頁面，並跳出你沒錢的訊息
    `flash("Your order be rejected if you do not have enough cash to cover this closing transaction.", 'danger')`
* 賣股票 `if action == 'Sell'`
賣股票檢查兩個地方，先檢查持股清單是不是空的，如果是空的直接導向前一個頁面並顯示錯誤訊息；再來檢查是不是有足夠的持股可以賣出，如果持股數量不足也會報錯
```
@admin_bp.route('/trade/review', methods=['GET', 'POST'])
@login_required
def review_order():
    cash = current_user.cash
    symbol = request.args.get('symbol')
    price = request.args.get('price')
    quantity = request.args.get('quantity')
    action = request.args.get('action')
    totalprice = float(price)*float(quantity)
    able_to_trade = True
    stocklist = []
    if action == 'Buy':
        cash_balance = float(cash) - totalprice
        if cash_balance < 0:
            able_to_trade = False
            flash("Your order be rejected if you do not have enough cash to cover this closing transaction.", 'danger')
            return redirect_back()
    else:
        cash_balance = float(cash) + totalprice
        portfolio = Portfolio.query.with_parent(current_user).all()
        # check if any existed portfolio, if no, return direct_back
        if portfolio:
        
            for company in portfolio:    
                stocklist.append(company.symbol)
            
            if symbol in stocklist:
                id = company.id
                stock = Portfolio.query.get_or_404(id)
                own_quantity = float(stock.quantity)
                if float(quantity) > own_quantity:
                    able_to_trade = False
                    flash("Your share isn't enough for the trade. the order was rejected", 'danger')
                    return redirect_back()
            else:
                flash("You dont have any share yet", 'danger')
                return redirect_back()

        else:
           able_to_trade = False
           flash("You portfolio is empty", 'danger')
           return redirect_back()
        
     
    return render_template("admin/review_order.html",able_to_trade = able_to_trade, action = action, symbol = symbol, price= price, quantity = quantity, cash = cash, totalprice=totalprice, cash_balance=cash_balance)

```
vfinance/template/admin/review_order.html
```
{% extends 'base.html' %}
{% from 'bootstrap/form.html' import render_form %}

{% block title %}Login{% endblock %}

{% block content %}
    <div >
        <div>
            <h1>{{ symbol }}: {{ price }}</h1>
        </div>
        
       <div>
           <h3><strong>{{ action }} {{ quantity }}</strong>
            {% if request.args.get('quantity') == '1' %}
            share
                           
            {% else %}
            shares 
            {% endif %}
            <strong>{{ symbol }}</strong>
            </h3>   
           
           
           
           <h3>Estimated trade amount: {{ totalprice }}</h3>
           <h3>Commission: $0.00</h3>
           
           <h3>cash: {{ cash }}</h3>
           <h3>Buying power after trade: {{cash_balance}}</h3>
           <form class="inline" method="post"
                 action="{{ url_for('admin.place_order', symbol = symbol, price = price, totalprice = totalprice, name = name, action = action, quantity = quantity, able_to_trade = able_to_trade,next=request.full_path) }}">
                <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
                <button type="submit" class="btn btn-success btn-sm">Approve</button>
            </form>

       </div>
    </div>
{% endblock %}

{% block footer %}{% endblock %}

```

## 送出交易單
```
@admin_bp.route('/trade/placeorder', methods=['GET', 'POST'])
@login_required
def place_order():
    # get trade condition
    able_to_trade = request.args.get('able_to_trade')
    action = request.args.get("action")
    if able_to_trade:
        # get original cash and position
        cash = float(current_user.cash)
        # position = float(current_user.position)
        

        # update cash and position back to database
        if action =="Buy":
            totalprice = float(request.args.get('totalprice'))
            current_user.cash = cash - totalprice
            # current_user.position = position + totalprice
        else:
            totalprice = float(request.args.get('totalprice'))
            current_user.cash = cash + totalprice
            # current_user.position = position - totalprice

        #update trade_history
        symbol = request.args.get('symbol')
        name = lookup(symbol)['name']
        price = request.args.get('price')
        quantity = request.args.get('quantity')
        
        
        
        trade_history = TradeHistory(symbol = symbol, name = name, price = price, action = action, quantity = quantity)
        db.session.add(trade_history)
        current_user.trade_history.append(trade_history)

        # update portfolio
        portfolio = Portfolio.query.with_parent(current_user).all()
        if portfolio:
            # check if the stock already existed
            for company in portfolio:
            # if exists update the averge pruchase price and quantity
                if symbol == company.symbol:
                    # get purchase price and symbol
                    id = company.id
                    stock = Portfolio.query.get_or_404(id)
                    purchase_price = float(stock.purchase_price)
                    own_quantity = float(stock.quantity)

                    # caculate new purchase price and quantity
                    if action =="Buy":
                        new_purchase_price = (purchase_price * own_quantity + float(quantity) * float(price))/(own_quantity + float(quantity))
                        new_own_quantity = own_quantity + float(quantity)
                    else:
                        new_own_quantity = own_quantity - float(quantity)
                        if new_own_quantity == 0:
                            db.session.delete(stock)
                            db.session.commit()
                            return redirect(url_for('home.index'))
        
                        new_purchase_price = (purchase_price*own_quantity - float(quantity) * float(price))/(own_quantity-float(quantity))
                        

                    stock.purchase_price = new_purchase_price
                    stock.quantity =new_own_quantity
                    db.session.commit()
                    return redirect(url_for("home.index"))
            # if not exist in portfolio, create it    
            new_portfolio = Portfolio(symbol = symbol, name = name, purchase_price = price, quantity = quantity)
            current_user.portfolio.append(new_portfolio)
        else:
            new_portfolio = Portfolio(symbol = symbol, name = name, purchase_price = price, quantity = quantity)
            current_user.portfolio.append(new_portfolio)

    db.session.commit()
    return redirect(url_for("home.index"))
```

用戶review完他的order確定無誤後送出，這時候place_order function就會開始更新用戶的帳戶餘額、交易紀錄、以及持股清單。這樣一來就完成了基本的買賣功能。 之後只要在模板上show出這些明細就好。