In the previous study, we set up the system registration and login functions, has basically met the needs of a small Web application. So if we want to make small money through this website, we need to provide more advanced functions, of course, these advanced functions are not free open, design a permission system, to control the use of advanced applications.

Writing advanced features

Advanced features are features that users will pay for, and fleece lovers like me will only use basic features.

The advanced feature I designed here is to enrich the K-chart by adding moving averages and volume on the basis of our original K-chart.

Moving average

Moving averages are very common in technical analysis. ‘Average’ refers to the average closing price per unit period, and ‘move’ refers to excluding the earliest closing price while incorporating the new trading day’s closing price into the calculation period.

Let’s take a look at the data from Tushare

import pyecharts.options as opts
from pyecharts.charts import Line


def moving_average() -> Line:
    c = (
        Line()
        .add_xaxis(df.index.tolist())
        .add_yaxis("Ma5", df['ma5'].values.tolist(), is_smooth=True)
        .add_yaxis("Ma10", df['ma10'].values.tolist(), is_smooth=True)
        .add_yaxis("Ma20", df['ma20'].values.tolist(), is_smooth=True)
        .set_global_opts(title_opts=opts.TitleOpts(title="Moving average"))
        .set_series_opts(
            label_opts=opts.LabelOpts(is_show=False),
        )
    )
    return c

moving_average().render_notebook()
Copy the code

volume

The volume can be shown in a bar chart. The height of the bar chart is the volume of the volume. Show the volume in red on the way up and green on the way down.

import pyecharts.options as opts
from pyecharts.charts import Line, Bar


volume_rise=[df.volume[x] if df.close[x] > df.open[x] else "0" for x in range(0, len(df.index))]
volume_drop=[df.volume[x] if df.close[x] <= df.open[x] else "0" for x in range(0, len(df.index))]


def volume() -> Bar:
    c = (
        Bar()
        .add_xaxis(df.index.tolist())
        .add_yaxis("volume_rise", volume_rise, stack=True, color=["#ec0000"])
        .add_yaxis("volume_drop", volume_drop, stack=True, color=["#00da3c"])
        .set_global_opts(title_opts=opts.TitleOpts(title="Volume"),
                        datazoom_opts=[opts.DataZoomOpts()],)
        .set_series_opts(
            label_opts=opts.LabelOpts(is_show=False),
        )
    )
    return c


volume().render_notebook()
Copy the code

Integrate three charts

Let’s combine the three charts, the K-chart, the moving average, and the volume chart. First, we stack the K-chart and the moving average together

def kline_base() -> Kline:
    kline = (
        Kline()
        .add_xaxis(df.index.tolist())
        .add_yaxis("Daily k-chart", df[['open'.'close'.'low'.'high']].values.tolist(), markpoint_opts=opts.MarkLineOpts(
            data=[opts.MarkLineItem(type_="max", value_dim="close")]
        ), markline_opts=opts.MarkLineOpts(
            data=[opts.MarkLineItem(type_="max", value_dim="close")]
        ),
                   itemstyle_opts=opts.ItemStyleOpts(
                       color="#ec0000",
                       color0="#00da3c",
                       border_color="#8A0000",
                       border_color0="#008F28",
                   ),
                   )
        .set_global_opts(
            yaxis_opts=opts.AxisOpts(is_scale=True,
                                    splitarea_opts=opts.SplitAreaOpts(
                    is_show=True, areastyle_opts=opts.AreaStyleOpts(opacity=1)
                ),
            ),
            xaxis_opts=opts.AxisOpts(is_scale=True,
                                    axislabel_opts=opts.LabelOpts(rotate=-30)),
            title_opts=opts.TitleOpts(title="Stock trend"),
            datazoom_opts=[opts.DataZoomOpts()],
            toolbox_opts=opts.ToolboxOpts(is_show=True),
        )
    )
    line = (
        Line()
        .add_xaxis(df.index.tolist())
        .add_yaxis("Ma5", df['ma5'].values.tolist(), is_smooth=True)
        .add_yaxis("Ma10", df['ma10'].values.tolist(), is_smooth=True)
        .add_yaxis("Ma20", df['ma20'].values.tolist(), is_smooth=True)
        .set_global_opts(title_opts=opts.TitleOpts(title="Moving average"))
        .set_series_opts(
            label_opts=opts.LabelOpts(is_show=False),
        )
    )
    kline.overlap(line)
    return kline
Copy the code

. bar = ( Bar() .add_xaxis(df.index.tolist()) .add_yaxis("volume_rise", volume_rise, stack=True, color=["#ec0000"], )
        .add_yaxis("volume_drop", volume_drop, stack=True, color=["#00da3c"], )
        .set_global_opts(title_opts=opts.TitleOpts(),
                        legend_opts=opts.LegendOpts(pos_right="20%"))
        .set_series_opts(
            label_opts=opts.LabelOpts(is_show=False),
    )
    )
    
    overlap_kline_line = kline.overlap(line)
    grid = Grid()
    grid.add(
        overlap_kline_line,
        grid_opts=opts.GridOpts(pos_left="10%", pos_right="8%", height="50%"),
    )
    grid.add(
        bar,
        grid_opts=opts.GridOpts(
            pos_left="10%", pos_right="8%", pos_top="70%", height="16%"),...Copy the code

Write individual chart pages

We’ll start by embedding the two newly generated charts into the Web application, each as a separate page

Backstage function

Start by creating functions that generate moving averages and volume charts

# Moving average
def moving_average_chart(mydate, data_5, data_10, data_20, name) -> Line:
    moving_average = (
        Line()
        .add_xaxis(mydate)
        .add_yaxis("ma5", data_5, is_smooth=True)
        .add_yaxis("ma10", data_10, is_smooth=True)
        .add_yaxis("ma20", data_20, is_smooth=True)
        .set_global_opts(title_opts=opts.TitleOpts(title="% S - Moving average" % name),
                         datazoom_opts=[opts.DataZoomOpts()],
                         )
        .set_series_opts(
            label_opts=opts.LabelOpts(is_show=False),
        )
    )
    return moving_average


# volume
def volume_chart(mydate, volume_rise, volume_drop, name) -> Bar:
    bar = (
        Bar()
        .add_xaxis(mydate)
        .add_yaxis("volume_rise", volume_rise, stack=True, color=["#ec0000"])
        .add_yaxis("volume_drop", volume_drop, stack=True, color=["#00da3c"])
        .set_global_opts(title_opts=opts.TitleOpts(title="%s- Volume" % name),
                        datazoom_opts=[opts.DataZoomOpts()],)
        .set_series_opts(
            label_opts=opts.LabelOpts(is_show=False),
        )
    )
    return bar
Copy the code

Then modify the get_stock_data function to return the data we need

def get_stock_data(code, ctime):
    df = ts.get_hist_data(code)
    df_time = df[:ctime]
    mydate = df_time.index.tolist()
    kdata = df_time[['open'.'close'.'low'.'high']].values.tolist()
    madata_5 = df_time['ma5'].values.tolist()
    madata_10 = df_time['ma10'].values.tolist()
    madata_20 = df_time['ma20'].values.tolist()
    volume_rise = [df_time.volume[x] if df_time.close[x] > df_time.open[x] else "0" for x in range(0, len(df_time.index))]
    volume_drop = [df_time.volume[x] if df_time.close[x] <= df_time.open[x] else "0" for x in range(0, len(df_time.index))]
    return [mydate, kdata, madata_5, madata_10, madata_20, volume_rise, volume_drop]
Copy the code

Then add the corresponding view function that generates the two charts

@app.route("/Line", methods=['GET'.'POST'])
def get_moving_average():
    stock_name = request.form.get('stockName')
    query_time = request.form.get('queryTime')
    if not stock_name:
        stock_name = Ping an Bank
    if not query_time:
        query_time = 30
    if int(query_time) > 30:
        if current_user.is_authenticated:
            pass
        else:
            abort(403)

    status, stock_code = check_stock(stock_name)
    if status == 0:
        return 'error stock code or name'
    mydate, kdata, madata_5, madata_10, madata_20, volume_rise, volume_drop = get_stock_data(stock_code[0], int(query_time))
    c = moving_average_chart(mydate, madata_5, madata_10, madata_20, stock_code[1])
    return c.dump_options()


@app.route("/Bar", methods=['GET'.'POST'])
def get_volume():
    stock_name = request.form.get('stockName')
    query_time = request.form.get('queryTime')
    if not stock_name:
        stock_name = Ping an Bank
    if not query_time:
        query_time = 30
    if int(query_time) > 30:
        if current_user.is_authenticated:
            pass
        else:
            abort(403)

    status, stock_code = check_stock(stock_name)
    if status == 0:
        return 'error stock code or name'
    mydate, kdata, madata_5, madata_10, madata_20, volume_rise, volume_drop = get_stock_data(stock_code[0], int(query_time))
    c = volume_chart(mydate, volume_rise, volume_drop, stock_code[1])
    return c.dump_options()
Copy the code

Then add the corresponding front end page

@app.route("/mavg", methods=['GET'.'POST'])
def moving_average():
    return render_template("mavg.html")


@app.route("/volume", methods=['GET'.'POST'])
def volume():
    return render_template("volume.html")
Copy the code

Finally create the two HTML files above and modify them

{% extends "base.html"%} {% block title %} My stock chart {% endblock %} {% block page_content %} {%for message in get_flashed_messages() %}
<div class="alert alert-warning">
     <button type="button" class="close" data-dismiss="alert"> &times; </button> {{ message }} </div> {% endfor %} <body> <div id="form-div">
         <form id="form1" onsubmit="return false" action="#" method="post">
             <p id="p1"< div style = "box-sizing: border-box"stockName" type="text" id="stockName" tabindex="1" size="16" value="" placeholder="Stock Name"/ > <! -- <inputtype="button" onclick="add1();" value="Add" />-->
             </p>
             <p id="p2"> Query time: <input name="queryTime" type="text" id="queryTime" tabindex="2" size="16" value="" placeholder="Enter 30 to query data of recent 30 days"/>
             </p>
             <p><input type="submit" value="Query" onclick="getData()"></p>
         </form>
     </div>
    <div id="Bar" style="width:1000px; height:600px;"></div>
    <script>
        $(
            function () {
                var chart = echarts.init(document.getElementById('Bar'), 'white', {renderer: 'canvas'});
                $.ajax({
                    type: "GET",
                    url: "http://127.0.0.1:5000/Bar",
                    dataType: 'json',
                    success: function(result) { chart.setOption(result); }}); });function getData() {
            var chart = echarts.init(document.getElementById('Bar'), 'white', {renderer: 'canvas'});
            $.ajax({
                type: "POST",// Method type dataType:"json"// The expected data type url from the server:"/Bar" ,//url
                data: $('#form1').serialize(),
                success: function (result) {
                    chart.setOption(result);
                },
                error: function(err) {
                    if (err.status === 403) {
                        alert("Please log in first!");
                    }
                    else {
                        alert("Wrong ticker symbol!"); }}}); }function add1(){
            var input1 = document.createElement('input');
            input1.setAttribute('type'.'text');
            input1.setAttribute('name'.'organizers[]');

            var btn1 = document.getElementById("p1");
            //btn1.insertBefore(input1,null);
            btn1.appendChild(input1);
        }
    </script>
</body>
{% endblock %}

{% block scripts %}
{{ super() }}
    <script src="https://cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
    <script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts.min.js"></script>
{% endblock %}
Copy the code

Also add the entry address to base.html

. <ul class="nav navbar-nav">
                <li><a href="{{ url_for('moving_average')}}">Moving Average</a></li>
            </ul>
            <ul class="nav navbar-nav">
                <li><a href="{{ url_for('volume')}}">Volume</a></li>
            </ul>
...
Copy the code

Now our Web application looks like this

Permission to design

Defining the table structure

Start by defining the permission table structure

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    users = db.relationship('WebUser', backref='role')

    @staticmethod
    def init_roles():
        roles = ['User'.'Admin']
        for r in roles:
            role = Role.query.filter_by(name=r).first()
            if role is None:
                role = Role(name=r)
                db.session.add(role)
        db.session.commit()
Copy the code

We have defined two permissions, User and Admin, so that only users with Admin permission can access advanced features.

There is also a foreign key associated with the WebUser table, so the WebUser table needs to be modified synchronously

User table structure
class WebUser(UserMixin, db.Model):
    __tablename__ = 'webuser'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.String(64), unique=True, index=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'), default=1)...Copy the code

Since we have modified the table structure of the original table, we need to migrate the table structure. We can use flask-Migrate to help us do this

Table structure migration

Flask-migrate will be installed first

pip install flask-migrate
Copy the code

Then configure Flask_migrate in your application

from flask_migrate import Migrate
...
migrate = Migrate(app, db, render_as_batch=True)
...
Copy the code

Create migration repository

flask db init
Copy the code

This command generates a migration folder in the current directory where all migration scripts are stored.

Creating a migration script

flask db migrate
Copy the code

The last step is to update the database, and if you use an SQllite database like me, you may need to make some changes to the migration script

Open the py file in versions under migrations and find the statement batch_op.create_foreign_key

batch_op.create_foreign_key('role_key'.'roles'['role_id'], ['id'])
Copy the code

Then run the following command

flask db upgrade
Copy the code

Finally, we initialize the roles and go to the flask shell to complete the initialization of the role table

flask shell
from app import Role
Role.init_roles()
Copy the code

This completes the migration and initialization of the database.

Permission to check

Now we are ready to write the permission verification section

Check the function

For the checksum function, we can write it in the WebUser class so that it can be called by current_user

. def is_admin(self):if self.role_id is 2:
            return True
        else:
            return False
...
Copy the code

Create a view that must be accessed by a user of the Admin role

@app.route('/fullchart/', methods=['GET'.'POST'])
@login_required
def fullchart():
    if current_user.is_admin():
        return "OK"
    flash('You have not permission to access this page')
    return redirect(url_for('index'))
Copy the code

Integration of front and rear ends

Add the page entry to the base.html page

<ul class="nav navbar-nav">
                <li><a href="{{ url_for('fullchart')}}">Full Chart</a></li>
            </ul>
Copy the code

Then create a new Full Chart function to generate the advanced chart

# full chart
def full_chart(mydate, kdata, data_5, data_10, data_20, volume_rise, volume_drop, name):
    kline = (
        Kline()
...
Copy the code

Similarly, write the interface functions provided for the front end

@app.route("/FullChart", methods=['GET'.'POST'])
def get_fullcharte():
    stock_name = request.form.get('stockName')
    query_time = request.form.get('queryTime')...Copy the code

Finally, create fullchart. HTML and modify it in response, pointing the fullchart view function to the template

@app.route('/fullchart/', methods=['GET'.'POST'])
@login_required
def fullchart():
    if current_user.is_admin():
        return render_template('fullchart.html')
    flash('You have not permission to access this page')
    return redirect(url_for('index'))
Copy the code

At this point, our advanced charting functionality is complete