2020年中青杯全國大學生數學建模競賽題目【本科組】——紀念第一次訓練模型!

一、題目:

B題(本科生組):股指與國家經濟

自1990年12月19日上海證券交易所掛牌成立,經過30年的快速發展,中國證券市場已經具有相當規模,在多方面取得了舉世矚目的成就,對國民經濟的資源配置起着日益重要的作用。截至2019年年底,上海和深圳兩個證券交易所交易的股票約4000種。目前,市場交易制度、信息披露制度和證券法規等配套制度體系已經建立起來,投資者日趨理性和成熟,機構投資者迅速發展已具規模,政府對證券市場交易和上市公司主體行爲的監管已見成效。
隨着近年來我國資本市場的發展和證券交易規模的不斷擴大,越來越多的資金投資於證券市場,與此同時市場價格的波動也十分劇烈,而波動作爲證券市場中最本質的屬性和特徵,市場的波動對於人們風險收益的分析、股東權益最大化和監管層的有效監管都有着至關重要的作用,因此研究證券市場波動的規律性,分析引起市場波動的成因,是證券市場理論研究和實證分析的重要內容,也可以爲投資者、監管者和上市公司等提供有跡可循的依據。
問題一:投資者購買目標指數中的資產,如果購買全部,從理論上講能夠完美跟蹤指數,但是當指數成分股較多時,購買所有資產的成本過於高昂,同時也需要很高的管理成本,在實際中一般不可行。
(1)在附件數據的分析和處理的過程中,請對缺損數據進行補全。
(2)投資者購買成分股時,過多過少都不太合理。對於附件的成分股數據,請您通過建立模型,給出合理選股方案和投資組合方案。
問題二:嘗試給出合理的評價指標來評估問題一中的模型,並給出您的分析結果。
問題三:通過附件股指數據和您補充的數據,對當前的指數波動和未來一年的指數波動進行合理建模,並給出您合理的投資建議和策略。
附件:十支股票的相關重要參數。

二、代碼分析:

①首先看一下我的文件夾目錄:

 大致說明一下:在這些文件及文件夾裏面,本科生組放在的是桌面上!然後是整體在Pycharm裏打開,所有生成的.idea是在第一級目錄!只有【附件:十支股票參數.xlsx】是必須要的,另外的文件夾都是通過os模塊創建的!

②因爲是第一次要訓練模型,百度了一波,發現股票的數據集都是csv,於是將這個Excel換成了csv!

"""
    1、將excel文件轉化爲:以各指數成分股票命名的csv文件,方便後面的pandas以及matplotlib和numpy的操作
"""

# 步驟一:將excel文件轉化爲csv文件
import os
import csv
from openpyxl import load_workbook

# 打開excel文件
wb = load_workbook('附件:十支股票參數.xlsx')
# 列出文件中所有的表名
sheets = wb.sheetnames

path = 'csv數據集'
if not os.path.exists(path):
    os.mkdir(path)

def create_csvs(title,path):
    name = title
    path = path
    f = open(path + '/' + name + '.csv',mode="a+",newline='',encoding="utf-8-sig")
    csv_write = csv.writer(f)
    csv_write.writerow(['Date', 'Open', 'High', 'Low','Close','Turnover'])
    f.close()

for sheet in sheets:
    create_csvs(sheet,path)
    f = open(path + '/' + sheet + '.csv',mode="a+",newline='',encoding="utf-8-sig")
    csv_write = csv.writer(f)
    table = wb[sheet]
    rows = table.max_row
    cols = table.max_column
    for row in range(5,rows+1):
        data = []
        for col in range(cols+1):
            data.append(str(table.cell(row,col+1).value).replace(r'/','-'))
        csv_write.writerow(data[:6])
    f.close()

代碼很好理解:通過wb.sheetnames將Excel中的所有表名都顯示出來,接着通過定義兩個函數分別生成csv模板以及將我們的Excel中的幾欄數據寫入到對應的CSV文件中!最後是通過for循環,將10支股票生成的csv文件都放到一個文件夾【csv數據集】中!(這個文件夾是通過os模塊創建的!)

csv數據集文件夾圖片:

 csv文件圖片:

 

③初步使用LSTM模型進行預測:

# 數據預處理以及繪製圖形需要的模塊
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 構建長短時神經網絡需要的方法
from sklearn.preprocessing import MinMaxScaler
from keras.models import Sequential
from keras.layers import Dense, LSTM, BatchNormalization
import os

# 需要之前50次的數據來預測下一次的數據
need_num = 50
# 訓練數據的大小
training_num = 200
# 迭代10次
epoch = 10
batch_size = 32
path = 'pics'
if not os.path.exists(path):
    os.mkdir(path)
# 訓練數據的處理,我們選取整個數據集的前200個數據作爲訓練數據,後面的數據爲測試數據
# 從csv讀取數據
csvs = ['csv數據集/abc001.csv','csv數據集/abc002.csv','csv數據集/abc003.csv','csv數據集/abc004.csv','csv數據集/abc005.csv','csv數據集/abc006.csv','csv數據集/abc007.csv','csv數據集/abc008.csv','csv數據集/abc009.csv','csv數據集/abc010.csv']
for j in csvs:
    dataset = pd.read_csv(j)
    # 我們需要預測開盤數據,因此選取所有行、第2列數據
    dataset = dataset.iloc[:, 4:5].values
    # 訓練數據就是上面已經讀取數據的前200行
    training_dataset = dataset[:training_num]
    # 因爲數據跨度幾十年,隨着時間增長,人民幣金額也隨之增長,因此需要對數據進行歸一化處理
    # 將所有數據歸一化爲0-1的範圍
    sc = MinMaxScaler(feature_range=(0, 1))
    '''
    fit_transform()對部分數據先擬合fit,
    找到該part的整體指標,如均值、方差、最大值最小值等等(根據具體轉換的目的),
    然後對該trainData進行轉換transform,從而實現數據的標準化、歸一化等等。
    '''
    training_dataset_scaled = sc.fit_transform(X=training_dataset)

    x_train = []
    y_train = []
    # 每10個數據爲一組,作爲測試數據,下一個數據爲標籤
    for i in range(need_num, training_dataset_scaled.shape[0]):
        x_train.append(training_dataset_scaled[i - need_num: i])
        y_train.append(training_dataset_scaled[i, 0])
    # 將數據轉化爲數組
    x_train, y_train = np.array(x_train), np.array(y_train)
    # 因爲LSTM要求輸入的數據格式爲三維的,[training_number, time_steps, 1],因此對數據進行相應轉化
    x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], 1))

    # 構建網絡,使用的是序貫模型
    model = Sequential()
    # return_sequences=True返回的是全部輸出,LSTM做第一層時,需要指定輸入shape
    model.add(LSTM(units=128, return_sequences=True, input_shape=[x_train.shape[1], 1]))
    model.add(BatchNormalization())

    model.add(LSTM(units=128))
    model.add(BatchNormalization())

    model.add(Dense(units=1))
    # 進行配置
    model.compile(optimizer='adam',
                  loss='mean_squared_error')
    model.fit(x=x_train, y=y_train, epochs=epoch, batch_size=batch_size)

    # 進行測試數據的處理
    # 前200個爲測試數據,但是將150,即200-50個數據作爲輸入數據,因爲這樣可以獲取
    # 測試數據的潛在規律
    inputs = dataset[training_num - need_num:]

    inputs = inputs.reshape(-1, 1)
    # 這裏使用的是transform而不是fit_transform,因爲我們已經在訓練數據找到了
    # 數據的內在規律,因此,僅使用transform來進行轉化即可
    inputs = sc.transform(X=inputs)
    x_validation = []

    for i in range(need_num, inputs.shape[0]):
        x_validation.append(inputs[i - need_num:i, 0])

    x_validation = np.array(x_validation)
    x_validation = np.reshape(x_validation, (x_validation.shape[0], x_validation.shape[1], 1))

    # 這是真實的股票價格,是源數據的[200:]即剩下的80個數據的價格
    real_stock_price = dataset[training_num:279]
    # 進行預測
    predictes_stock_price = model.predict(x=x_validation)
    # 使用 sc.inverse_transform()將歸一化的數據轉換回原始的數據,以便我們在圖上進行查看
    predictes_stock_price = sc.inverse_transform(X=predictes_stock_price)

    # 繪製數據圖表,紅色是真實數據,藍色是預測數據
    plt.plot(real_stock_price, color='red', label='Real Stock Price')
    plt.plot(predictes_stock_price, color='blue', label='Predicted Stock Price')
    plt.title(label='Close Price Prediction')
    plt.xlabel(xlabel='Time')
    plt.ylabel(ylabel=j+'Close')
    plt.savefig(path + '/' + j.replace(r'csv數據集/','').replace(r'.csv','') + '-Close.png')
    plt.legend()
    plt.show()

其中呢,我們需要修改這個代碼幾次,因爲上面的這個代碼得出的是【10支股票收盤的預測價格】,我們需要將Close換成Open/High/Low/Tow/Turnover,以及dataset = dataset.iloc[:, 4:5].values所代表的切片的列數!

雖然結果有點差強人意,但是好歹是預測了:

 ④使用均值-方差模型進行波動評價:

數統的小姐姐提供的思路,我覺得可行,就寫了一下代碼:

import csv
import os

files = os.listdir('csv數據集')
path = '處理後csv數據集'
if not os.path.exists(path):
    os.mkdir(path)
max_a = []
max_b = []
for file in files:
    Date = [row[0] for row in csv.reader(open('csv數據集' + '/' + file,mode="r",encoding="utf-8"))]
    Open = [row[1] for row in csv.reader(open('csv數據集' + '/' + file,mode="r",encoding="utf-8"))]
    High = [row[2] for row in csv.reader(open('csv數據集' + '/' + file,mode="r",encoding="utf-8"))]
    Low = [row[3] for row in csv.reader(open('csv數據集' + '/' + file,mode="r",encoding="utf-8"))]
    Close = [row[4] for row in csv.reader(open('csv數據集' + '/' + file,mode="r",encoding="utf-8"))]
    Turnover = [row[5] for row in csv.reader(open('csv數據集' + '/' + file,mode="r",encoding="utf-8"))]
    f = open(path + '/' + file,mode="a+",newline="",encoding="utf-8-sig")
    csv_write = csv.writer(f)
    csv_write.writerow(['Date','Open','High','Low','Close','Turnover','收盤-開盤(a)','最高-最低(b)'])
    sum_a = 0           # 求均值
    sum_b = 0
    variance_a = 0      # 求方差
    variance_b = 0
    for i in range(1,len(Date)-1):
        a = float(Close[i]) - float(Open[i])
        sum_a += a
        b = float(High[i]) - float(Low[i])
        sum_b += b
    for j in range(1,len(Date)-1):
        a = float(Close[j]) - float(Open[j])
        b = float(High[j]) - float(Low[j])
        variance_a += ((a-sum_a/280)**2)
        variance_b += ((b-sum_b/280)**2)
    for k in range(1,len(Date)):
        a = float(Close[k]) - float(Open[k])
        b = float(High[k]) - float(Low[k])
        csv_write.writerow([Date[k],Open[k],High[k],Low[k],Close[k],Turnover[k],a,b])

    csv_write.writerow('\n')
    csv_write.writerow(['a的均值','a的方差','a(均值/方差)','b的均值','b的方差','b(均值/方差)'])
    max_a.append(sum_a/variance_a)
    max_b.append(sum_b/variance_b)
    csv_write.writerow([sum_a/279,variance_a/279,sum_a/variance_a,sum_b/279,variance_b/279,sum_b/variance_b])
    f.close()

# 給10支股票打分
fp = open(path + '/'+'10支股票打分情況.txt',mode="w",encoding='utf-8')
fp.write("10支股票(均值/方差)最大值:"+"\n")
fp.write("\t"+"①收益(a)最高分:"+"\n")
fp.write("\t"+str(max(max_a)) + "\n")
fp.write("\t"+"②風險(b)最高分:"+"\n")
fp.write("\t"+str(max(max_b)) + "\n")
fp.write("*"*100+"\n")
fp.write("10支股票(均值/方差)所有數值:"+"\n")
fp.write("\t"+"①收益(a)數值情況:"+"\n")
fp.write("\t"+str(max_a) + "\n")
fp.write("\t"+"②風險(b)數值情況:"+"\n")
fp.write("\t"+str(max_b) + "\n")
fp.write("*"*100+"\n")
fp.write("下面是收益(a)相對最高分時的打分情況"+"\n")
sum_a_average = 0
sum_b_average = 0
for i in range(len(max_a)):
    fp.write("\t"+files[i].replace(r'.csv','')+":"+"\t"+str((max_a[i]/max(max_a))*100)+"\n")
    sum_a_average += (max_a[i]/max(max_a))*100
fp.write("\t"+"收益打分平均值:"+"\t"+str(sum_a_average/10)+"\n")
fp.write("*"*100+"\n")
fp.write("下面是風險(b)相對最高分時的打分情況"+"\n")
for i in range(len(max_b)):
    fp.write("\t"+files[i].replace(r'.csv','')+":"+"\t"+str((max_b[i]/max(max_b))*100)+"\n")
    sum_b_average += (max_b[i]/max(max_b))*100
fp.write("\t"+"風險打分平均值:"+"\t"+str(sum_b_average/10)+"\n")

思路是:計算收益(a) = 收盤-開盤,風險(b) = 最高-最低!然後呢得出10支股票的每一行的數據,寫到新的csv文件的對應的列後面!接着,計算整列的a以及b分別對應的均值以及方差!寫到csv文件的結尾處!然後,遍歷10支股票的(均值/方差)最大值並設置成100分,分別得出10支股票的得分!

 看一下打分情況:

後面的分析數統隊友的事了,反正我也看不懂~~~

 

⑤新的模型測試:

# 根據股票歷史的開盤價、收盤價和成交量等特徵值,從數學角度來預測股票未來的收盤價
import pandas as pd
import numpy as np
import math
import os
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

# 從文中獲取數據
files = os.listdir('csv數據集')
path = '第二種模型預測圖片'
if not os.path.exists(path):
    os.mkdir(path)
for file in files:
    origDf = pd.read_csv('csv數據集'+'/'+file, encoding='utf-8-sig')
    df = origDf[['Close', 'High', 'Low', 'Open', 'Turnover']]
    featureData = df[['Open', 'High', 'Turnover','Low']]
    # 劃分特徵值和目標值
    feature = featureData.values
    """
        設置了要預測的目標列是收盤價。在後續的代碼中,需要將計算出開盤價、最高價、最低價和成交量
        這四個特徵值和收盤價的線性關係,並在此基礎上預測收盤價。
    """
    target = np.array(df['Turnover'])
    # 劃分訓練集/測試集
    feature_train, feature_test, target_train, target_test = train_test_split(feature, target, test_size=0.05)
    """
        通過調用train_test_split方法把包含在csv文件中的股票數據分成訓練集和測試集,
        這個方法前兩個參數分別是特徵列和目標列,而第三個參數0.05則表示測試集的大小是總量的0.05。
        該方法返回的四個參數分別是特徵值的訓練集、特徵值的測試集、要預測目標列的訓練集和目標列的測試集。
    """
    pridectedDays = int(math.ceil(0.05 * len(origDf)))  # 預測天數
    lrTool = LinearRegression()                         # 建了一個線性迴歸預測的對象
    lrTool.fit(feature_train, target_train)  # 調用fit方法訓練特徵值和目標值的線性關係,請注意這裏的訓練是針對訓練集的
    # 用特徵值的測試集來預測目標值(即收盤價)。也就是說,是用多個交易日的股價來訓練lrTool對象,並在此基礎上預測後續交易日的收盤價
    predictByTest = lrTool.predict(feature_test)


    # 組裝數據
    index = 0
    # 在前95%的交易日中,設置預測結果和收盤價一致
    while index < len(origDf) - pridectedDays:
        # 把訓練集部分的預測股價設置成收盤價
        df.ix[index, 'predictedVal'] = origDf.ix[index, 'Turnover']
        # 設置了訓練集部分的日期
        df.ix[index, 'Date'] = origDf.ix[index, 'Date']
        index = index + 1
    predictedCnt = 0

    # 在後5%的交易日中,用測試集推算預測股價
    while predictedCnt < pridectedDays:
        df.ix[index, 'predictedVal'] = predictByTest[predictedCnt]
        # 把df中表示測試結果的predictedVal列設置成相應的預測結果,同時也在後面的程序語句逐行設置了每條記錄中的日期
        df.ix[index, 'Date'] = origDf.ix[index, 'Date']
        predictedCnt = predictedCnt + 1
        index = index + 1

    plt.figure()
    # 分別繪製了預測股價和真實收盤價,在繪製的時候設置了不同的顏色,也設置了不同的label標籤值
    df['predictedVal'].plot(color="red", label='predicted Data')
    df['Turnover'].plot(color="blue", label='Real Data')
    # 通過調用legend方法,根據收盤價和預測股價的標籤值,繪製了相應的圖例
    plt.legend(loc='best')  # 繪製圖例
    # 設置x座標的標籤
    # 設置了x軸顯示的標籤文字是日期,爲了不讓標籤文字顯示過密,設置了“每20個日期裏只顯示1個”的顯示方式
    major_index = df.index[df.index % 20 == 0]
    major_xtics = df['Date'][df.index % 20 == 0]
    plt.xticks(major_index, major_xtics)
    plt.setp(plt.gca().get_xticklabels(), rotation=30)

    # 帶網格線,且設置了網格樣式
    plt.grid(linestyle='-.')
    plt.savefig(path + '/' + file.replace(r".csv",'-Turnover.png'))
    plt.show()
    """預測股價和真實價之間有差距,但漲跌的趨勢大致相同。而且在預測時沒有考慮到漲跌停的因素,所以預測結果的漲跌幅度比真實數據要大"""

 相對於之前的LSTM模型來說,這個訓練和測試更直觀一點!代碼跟之前一樣,也需要手動的修改幾次!

這邊的情況是:將數據集的95%作爲訓練集,剩下的5%作爲測試集,然後分別用紅線和藍線表示!

 然後,我們就可以根據這個進行預測第280行數據(原文件中280行數據可能是錯的!所以要補全~)

⑥以上這麼多是問題一以及問題二的解決思路!至於問題三,隊友用SPSS預測的!說實話,我沒思路~~~

 

三、總結

寫下這篇博客的目的:

  • 六一兒童節快樂~~~
  • 紀念一下自己的第一次模型使用~~(爲後面的學習找到理由)
  • 代碼寫了幾個小時,難道不能亮出來麼~~~

本文代碼及材料見Github倉庫!(點擊就可以實現跳轉~)

微信公衆號:【空谷小莜藍】,一個普通985的精神小夥創建的公衆號,大家可以關注一下哈~~~

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章