Numpy

NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。

numpy概述

  1. Numerical Python,数值的Python,补充了Python语言所欠缺的数值计算能力。
  2. Numpy是其它数据分析及机器学习库的底层库。
  3. Numpy完全标准C语言实现,运行效率充分优化。
  4. Numpy开源免费。

numpy历史

  1. 1995年,Numeric,Python语言数值计算扩充。
  2. 2001年,Scipy->Numarray,多维数组运算。
  3. 2005年,Numeric+Numarray->Numpy。
  4. 2006年,Numpy脱离Scipy成为独立的项目。

numpy的核心:多维数组

  1. 代码简洁:减少Python代码中的循环。
  2. 底层实现:厚内核(C)+薄接口(Python),保证性能。

numpy基础

ndarray数组

用np.ndarray类的对象表示n维数组

1
2
3
import numpy as np
ary = np.array([1, 2, 3, 4, 5, 6])
print(type(ary))

内存中的ndarray对象

元数据(metadata)

存储对目标数组的描述信息,

字段 描述
dim
count 长度
dimensions
dtype 类型
data 数据

实际数据

完整的数组数据

将实际数据与元数据分开存放,一方面提高了内存空间的使用效率,另一方面减少对实际数据的访问频率,提高性能。

ndarray数组对象的特点

  1. Numpy数组是同质数组,即所有元素的数据类型必须相同
  2. Numpy数组的下标从0开始,最后一个元素的下标为数组长度减1

ndarray数组对象的创建

np.array(任何可被解释为Numpy数组的逻辑结构)

1
2
3
import numpy as np
a = np.array([1, 2, 3, 4, 5, 6])
print(a)

np.arange(起始值(0),终止值,步长(1))

1
2
3
4
5
6
import numpy as np
# arange 生成一个等差数列
a = np.arange(0, 5, 1)
print(a)
b = np.arange(0, 10, 2)
print(b)

np.zeros(数组元素个数, dtype=’类型’)

1
2
3
import numpy as np
a = np.zeros(10)
print(a)

np.ones(数组元素个数, dtype=’类型’)

1
2
3
import numpy as np
a = np.ones(10)
print(a)

ndarray对象属性的基本操作

数组的维度:np.ndarray.shape

1
2
3
4
5
6
7
8
9
import numpy as np
ary = np.array([1, 2, 3, 4, 5, 6])
print(type(ary), ary, ary.shape)
#二维数组
ary = np.array([
[1,2,3,4],
[5,6,7,8]
])
print(type(ary), ary, ary.shape)

元素的类型:np.ndarray.dtype

1
2
3
4
5
6
7
8
9
10
# test ndarray.dtype
ary = np.array([1, 2, 3, 4])
print(ary, ary.dtype)
# ary.dtype = 'int64' error
# print(ary, ary.dtype)
# 源数组类型不变
ary = ary.astype('float64') # 返回新的对象
print(ary, ary.dtype)
ary = ary.astype('str') # 返回新的对象
print(ary, ary.dtype)

数组元素的个数:np.ndarray.size

1
2
3
4
5
6
7
import numpy as np
ary = np.array([
[1,2,3,4],
[5,6,7,8]
])
#观察维度,size,len的区别
print(ary.shape, ary.size, len(ary))

数组元素索引(下标)

数组对象[…, 页号, 行号, 列号]

下标从0开始,到数组len-1结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
a = np.array([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]])
print(a, a.shape)
print(a[0])
print(a[0][0])
print(a[0][0][0])
print(a[0, 0, 0])
for i in range(a.shape[0]):
for j in range(a.shape[1]):
for k in range(a.shape[2]):
print(a[i, j, k])

ndarray对象属性操作详解

Numpy的内部基本数据类型

类型名 类型表示符
布尔型 bool_
有符号整数型 int8(-128~127)/int16/int32/int64
无符号整数型 uint8(0~255)/uint16/uint32/uint64
浮点型 float16/float32/float64
复数型 complex64/complex128 3
字串型 str_,每个字符用32位Unicode编码表示

自定义复合类型
同时演示设置dtype的几种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import numpy as np

data = [('zs', [50,51,52], 15),
('ls', [83,71,62], 16),
('ww', [90,91,92], 17)]

#第一种dtype的设置方式
ary = np.array(data,dtype='U2, 3int32, int32')
print(ary, ary[0][1])
print(ary[0]['f0'])

#第二种dtype的设置方式
ary = np.array(data,
dtype=[ ('name', 'str', 2),
('scores', 'int32', 3),
('age', 'int32', 1)])
print('-' * 45)
print(ary, ary.dtype)
print(ary[0]['age']) # 返回zs的年龄
print(ary[2]['scores']) # 返回ww的成绩

# 第三种dtype的设置方式
ary = np.array(data, dtype={
'names':['name', 'scores', 'age'],
'formats':['U2', '3int32', 'int32']})
print(ary)
print(ary[0]['age']) # 返回zs的年龄
print(ary[2]['scores']) # 返回ww的成绩


# 第四种dtype的设置方式
d = np.array(data, dtype={'name': ('U3', 0),
'scores': ('3int32', 16),
'age': ('int32', 28)})
print(d[0]['name'], d[0]['scores'], d.itemsize)

# ndarray数组存放日期数据
dates = ['2011-01-01', '2012-01-01',
'2011-02-01', '2012',
'2011-01-01 10:10:10']
ary = np.array(dates)
print(ary, ary.dtype)
ary = ary.astype('M8[D]')
print(ary, ary.dtype, ary[1]-ary[0])
# 输出31 days

类型字符码

类型 字符码
np.bool_ ?
np.int8/16/32/64 i1/i2/i4/i8
np.uint8/16/32/64 u1/u2/u4/u8
np.float/16/32/64 f2/f4/f8
np.complex64/128 c8/c16
np.str_ U<字符数>
np.datetime64 M8[Y] M8[M] M8[D] M8[h] M8[m] M8[s]

字节序前缀,用于多字节整数和字符串:
</>/[=]分别表示小端/大端/硬件字节序。

类型字符码格式

<字节序前缀><维度><类型><字节数或字符数>

3i4 释义
3i4 3个元素的一维数组,每个元素都是整型,每个整型元素占4个字节。
<(2,3)u8 小端字节序,6个元素2行3列的二维数组,每个元素都是无符号整型,每个无符号整型元素占8个字节。
U7 包含7个字符的Unicode字符串,每个字符占4个字节,采用默认字节序。
ndarray数组对象的维度操作

视图变维(数据共享): reshape() 与 ravel()

1
2
3
4
5
6
7
8
9
import numpy as np
a = np.arange(1, 9)
print(a) # [1 2 3 4 5 6 7 8]
b = a.reshape(2, 4) #视图变维 : 变为2行4列的二维数组
print(b)
c = b.reshape(2, 2, 2) #视图变维 变为2页2行2列的三维数组
print(c)
d = c.ravel() #视图变维 变为1维数组
print(d)

复制变维(数据独立):flatten()

1
2
3
4
e = c.flatten()
print(e)
a += 10
print(a, e, sep='\n')

就地变维:直接改变原数组对象的维度,不返回新数组

1
2
3
4
a.shape = (2, 4)
print(a)
a.resize(2, 2, 2)
print(a)
ndarray数组切片操作
1
2
3
4
5
#数组对象切片的参数设置与列表切面参数类似
# 步长+:默认切从首到尾
# 步长-:默认切从尾到首
数组对象[起始位置:终止位置:步长]
#默认位置步长:1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
a = np.arange(1, 10)
print(a) # 1 2 3 4 5 6 7 8 9
print(a[:3]) # 1 2 3
print(a[3:6]) # 4 5 6
print(a[6:]) # 7 8 9
print(a[::-1]) # 9 8 7 6 5 4 3 2 1
print(a[:-4:-1]) # 9 8 7
print(a[-4:-7:-1]) # 6 5 4
print(a[-7::-1]) # 3 2 1
print(a[::]) # 1 2 3 4 5 6 7 8 9
print(a[:]) # 1 2 3 4 5 6 7 8 9
print(a[::3]) # 1 4 7
print(a[1::3]) # 2 5 8
print(a[2::3]) # 3 6 9

多维数组的切片操作

1
2
3
4
5
6
7
8
# 多维数组切片
print('-' * 45)
a = np.arange(1, 10)
a = a.reshape(3, 3)
print(a)
print(a[:2, :]) # 切出前两行数据
print(a[:2, :2]) # 切出前两行两列数据
print(a[::2, :]) #
ndarray数组的掩码操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
a = np.array([1, 2, 3, 4, 5])
print(a + 10)
print(a * 2.5)
print(a + a)

# 输出100以内3的倍数
a = np.arange(1, 10)
mask = a % 3 == 0
print(mask)
print(a[mask])

mask = [2, 2, 3, 3, 6, 6, 4, 4]
print(a)
print(a[mask])

常用语从大数组中获取子集的操作

多维数组的组合与拆分

垂直方向操作:

1
2
3
4
5
6
7
import numpy as np
a = np.arange(1, 7).reshape(2, 3)
b = np.arange(7, 13).reshape(2, 3)
# 垂直方向完成组合操作,生成新数组
c = np.vstack((a, b))
# 垂直方向完成拆分操作,生成两个数组
d, e = np.vsplit(c, 2)

水平方向操作:

1
2
3
4
5
6
7
import numpy as np
a = np.arange(1, 7).reshape(2, 3)
b = np.arange(7, 13).reshape(2, 3)
# 水平方向完成组合操作,生成新数组
c = np.hstack((a, b))
# 水平方向完成拆分操作,生成两个数组
d, e = np.hsplit(c, 2)

深度方向操作:(3维)

1
2
3
4
5
6
7
import numpy as np
a = np.arange(1, 7).reshape(2, 3)
b = np.arange(7, 13).reshape(2, 3)
# 深度方向(3维)完成组合操作,生成新数组
i = np.dstack((a, b))
# 深度方向(3维)完成拆分操作,生成两个数组
k, l = np.dsplit(i, 2)

多维数组组合与拆分的相关函数:

1
2
3
4
5
6
7
8
9
10
11
# 通过axis作为关键字参数指定组合的方向,取值如下:
# 若待组合的数组都是二维数组:
# 0: 垂直方向组合
# 1: 水平方向组合
# 若待组合的数组都是三维数组:
# 0: 垂直方向组合
# 1: 水平方向组合
# 2: 深度方向组合
np.concatenate((a, b), axis=0)
# 通过给出的数组与要拆分的份数,按照某个方向进行拆分,axis的取值同上
np.split(c, 2, axis=0)

长度不等的数组组合:

1
2
3
4
5
6
7
8
9
10
import numpy as np
a = np.array([1,2,3,4,5])
b = np.array([1,2,3,4])
# 填充b数组使其长度与a相同
# pad_width=(a, b):在数组首部补a个元素,尾部补b个元素
b = np.pad(b, pad_width=(0, 1), mode='constant', constant_values=-1)
print(b)
# 垂直方向完成组合操作,生成新数组
c = np.vstack((a, b))
print(c)

简单的一维数组组合方案

1
2
3
4
5
6
7
8
a = np.arange(1,9)		#[1, 2, 3, 4, 5, 6, 7, 8]
b = np.arange(9,17) #[9,10,11,12,13,14,15,16]
#把两个数组摞在一起成两行
c = np.row_stack((a, b))
print(c)
#把两个数组组合在一起成两列
d = np.column_stack((a, b))
print(d)

ndarray类的其他属性

  • shape - 维度
  • dtype - 元素类型
  • size - 元素数量
  • ndim - 维数,len(shape)
  • itemsize - 元素字节数
  • nbytes - 总字节数 = size x itemsize
  • real - 复数数组的实部数组
  • imag - 复数数组的虚部数组
  • T - 数组对象的转置视图
  • flat - 扁平迭代器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
a = np.array([[1 + 1j, 2 + 4j, 3 + 7j],
[4 + 2j, 5 + 5j, 6 + 8j],
[7 + 3j, 8 + 6j, 9 + 9j]])
print(a.shape)
print(a.dtype)
print(a.ndim)
print(a.size)
print(a.itemsize)
print(a.nbytes)
print(a.real, a.imag, sep='\n')
print(a.T)
print([elem for elem in a.flat])
b = a.tolist()
print(b)

相关函数

算数平均值

1
S = [s1, s2, ..., sn]

样本中的每个值都是真值与误差的和。

1
2
算数平均值:
m = (s1 + s2 + ... + sn) / n

算数平均值表示对真值的无偏估计。

1
np.mean(array)

案例:计算收盘价的算术平均值。

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
closing_prices = np.loadtxt(
'../../data/aapl.csv', delimiter=',',
usecols=(6), unpack=True)
mean = 0
for closing_price in closing_prices:
mean += closing_price
mean /= closing_prices.size
print(mean)
mean = np.mean(closing_prices)
print(mean)

加权平均值

样本:S = [s1, s2, …, sn]

权重:W = [w1, w2, …, wn]

加权平均值:a = (s1w1+s2w2+…+snwn)/(w1+w2+…+wn)

1
np.average(closing_prices, weights=volumes)

VWAP - 成交量加权平均价格(成交量体现了市场对当前交易价格的认可度,成交量加权平均价格将会更接近这支股票的真实价值)

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np
closing_prices, volumes = np.loadtxt(
'../../data/aapl.csv', delimiter=',',
usecols=(6, 7), unpack=True)
vwap, wsum = 0, 0
for closing_price, volume in zip(
closing_prices, volumes):
vwap += closing_price * volume
wsum += volume
vwap /= wsum
print(vwap)
vwap = np.average(closing_prices, weights=volumes)
print(vwap)

TWAP - 时间加权平均价格(时间越晚权重越高,参考意义越大)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import datetime as dt
import numpy as np

def dmy2days(dmy):
dmy = str(dmy, encoding='utf-8')
date = dt.datetime.strptime(dmy, '%d-%m-%Y').date()
days = (date - dt.date.min).days
return days

days, closing_prices = np.loadtxt(
'../../data/aapl.csv', delimiter=',',
usecols=(1, 6), unpack=True,
converters={1: dmy2days})
twap = np.average(closing_prices, weights=days)
print(twap)

最值

np.max() np.min() np.ptp(): 返回一个数组中最大值/最小值/极差

1
2
3
4
5
import numpy as np
# 产生9个介于[10, 100)区间的随机数
a = np.random.randint(10, 100, 9)
print(a)
print(np.max(a), np.min(a), np.ptp(a))

np.argmax() mp.argmin(): 返回一个数组中最大/最小元素的下标

1
print(np.argmax(a), np.argmin(a))

np.maximum() np.minimum(): 将两个同维数组中对应元素中最大/最小元素构成一个新的数组

1
print(np.maximum(a, b), np.minimum(a, b), sep='\n')

案例:评估AAPL股票的波动性。

1
2
3
4
5
6
7
import numpy as np
highest_prices, lowest_prices = np.loadtxt(
'../../data/aapl.csv', delimiter=',',
usecols=(4, 5), dtype='f8, f8', unpack=True)
max_price = np.max(highest_prices)
min_price = np.min(lowest_prices)
print(min_price, '~', max_price)

查看AAPL股票最大最小值的日期,分析为什么这一天出现最大最小值。

1
2
3
4
5
6
7
8
import numpy as np
dates, highest_prices, lowest_prices = np.loadtxt(
'../../data/aapl.csv', delimiter=',',
usecols=(1, 4, 5), dtype='U10, f8, f8',
unpack=True)
max_index = np.argmax(highest_prices)
min_index = np.argmin(lowest_prices)
print(dates[min_index], dates[max_index])

观察最高价与最低价的波动范围,分析这支股票底部是否坚挺。

1
2
3
4
5
6
7
8
import numpy as np
dates, highest_prices, lowest_prices = np.loadtxt(
'../../data/aapl.csv', delimiter=',',
usecols=(1, 4, 5), dtype='U10, f8, f8',
unpack=True)
highest_ptp = np.ptp(highest_prices)
lowest_ptp = np.ptp(lowest_prices)
print(lowest_ptp, highest_ptp)

中位数

将多个样本按照大小排序,取中间位置的元素。

若样本数量为奇数,中位数为最中间的元素

1 2000 3000 4000 10000000

若样本数量为偶数,中位数为最中间的两个元素的平均值

1 2000 3000 4000 5000 10000000

案例:分析中位数的算法,测试numpy提供的中位数API:

1
2
3
4
5
6
7
8
9
import numpy as np
closing_prices = np.loadtxt( '../../data/aapl.csv',
delimiter=',', usecols=(6), unpack=True)
size = closing_prices.size
sorted_prices = np.msort(closing_prices)
median = (sorted_prices[int((size - 1) / 2)] + sorted_prices[int(size / 2)]) / 2
print(median)
median = np.median(closing_prices)
print(median)

标准差

样本:S = [s1, s2, …, sn]
平均值:m = (s1+s2+…+sn)/n
离差:D = [d1, d2, …, dn], di = si-m
离差方:Q = [q1, q2, …, qn], qi = di2
总体方差:v = (q1+q2+…+qn)/n
总体标准差:s = sqrt(v),方均根
样本方差:v’ = (q1+q2+…+qn)/(n-1)
样本标准差:s’ = sqrt(v’),方均根

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
closing_prices = np.loadtxt(
'../../data/aapl.csv', delimiter=',', usecols=(6), unpack=True)
mean = np.mean(closing_prices) # 算数平均值
devs = closing_prices - mean # 离差
dsqs = devs ** 2 # 离差方
pvar = np.sum(dsqs) / dsqs.size # 总体方差
pstd = np.sqrt(pvar) # 总体标准差
svar = np.sum(dsqs) / (dsqs.size - 1) # 样本方差
sstd = np.sqrt(svar) # 样本标准差
print(pstd, sstd)
pstd = np.std(closing_prices) # 总体标准差
sstd = np.std(closing_prices, ddof=1) # 样本标准差
print(pstd, sstd)

时间数据处理

案例:统计每个周一、周二、…、周五的收盘价的平均值,并放入一个数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import datetime as dt
import numpy as np

# 转换器函数:将日-月-年格式的日期字符串转换为星期
def dmy2wday(dmy):
dmy = str(dmy, encoding='utf-8')
date = dt.datetime.strptime(dmy, '%d-%m-%Y').date()
wday = date.weekday() # 用 周日
return wday

wdays, closing_prices = np.loadtxt('../data/aapl.csv', delimiter=',',
usecols=(1, 6), unpack=True, converters={1: dmy2wday})

ave_closing_prices = np.zeros(5)
for wday in range(ave_closing_prices.size):
ave_closing_prices[wday] = closing_prices[wdays == wday].mean()

for wday, ave_closing_price in zip(
['MON', 'TUE', 'WED', 'THU', 'FRI'],
ave_closing_prices):
print(wday, np.round(ave_closing_price, 2))

数组的轴向汇总

案例:汇总每周的最高价,最低价,开盘价,收盘价。

1
2
3
4
5
6
def func(data):
pass
#func 处理函数
#axis 轴向 [0,1]
#array 数组
np.apply_along_axis(func, axis, array)

沿着数组中所指定的轴向,调用处理函数,并将每次调用的返回值重新组织成数组返回。

移动均线

收盘价5日均线:从第五天开始,每天计算最近五天的收盘价的平均值所构成的一条线。

移动均线算法:

1
2
3
4
5
(a+b+c+d+e)/5
(b+c+d+e+f)/5
(c+d+e+f+g)/5
...
(f+g+h+i+j)/5

在K线图中绘制5日均线图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import datetime as dt
import numpy as np
import matplotlib.pyplot as mp
import matplotlib.dates as md

def dmy2ymd(dmy):
dmy = str(dmy, encoding='utf-8')
date = dt.datetime.strptime(dmy, '%d-%m-%Y').date()
ymd = date.strftime('%Y-%m-%d')
return ymd

dates, closing_prices = np.loadtxt('../data/aapl.csv', delimiter=',',
usecols=(1, 6), unpack=True, dtype='M8[D], f8', converters={1: dmy2ymd})
sma51 = np.zeros(closing_prices.size - 4)
for i in range(sma51.size):
sma51[i] = closing_prices[i:i + 5].mean()
# 开始绘制5日均线
mp.figure('Simple Moving Average', facecolor='lightgray')
mp.title('Simple Moving Average', fontsize=20)
mp.xlabel('Date', fontsize=14)
mp.ylabel('Price', fontsize=14)
ax = mp.gca()
# 设置水平坐标每个星期一为主刻度
ax.xaxis.set_major_locator(md.WeekdayLocator( byweekday=md.MO))
# 设置水平坐标每一天为次刻度
ax.xaxis.set_minor_locator(md.DayLocator())
# 设置水平坐标主刻度标签格式
ax.xaxis.set_major_formatter(md.DateFormatter('%d %b %Y'))
mp.tick_params(labelsize=10)
mp.grid(linestyle=':')
dates = dates.astype(md.datetime.datetime)
mp.plot(dates, closing_prices, c='lightgray', label='Closing Price')
mp.plot(dates[4:], sma51, c='orangered', label='SMA-5(1)')
mp.legend()
mp.gcf().autofmt_xdate()
mp.show()

卷积

激励函数:g(t)

单位激励下的响应函数:f(t)

绘制时间(t)与痛感(h)的函数关系图。

a = [1 2 3 4 5] (理解为某单位时间的击打力度序列)

b = [6 7 8] (理解为痛感系数序列)

1
2
3
4
5
6
7
8
9
10
11
12
13
c = numpy.convolve(a, b, 卷积类型)

40 61 82 - 有效卷积(valid)
19 40 61 82 67 - 同维卷积(same)
6 19 40 61 82 67 40 - 完全卷积(full)
0 0 1 2 3 4 5 0 0
8 7 6
8 7 6
8 7 6
8 7 6
8 7 6
8 7 6
8 7 6

5日移动均线序列可以直接使用卷积实现

1
2
a = [a, b, c, d, e, f, g, h, i, j] 
b = [1/5, 1/5, 1/5, 1/5, 1/5]

使用卷积函数numpy.convolve(a, b, 卷积类型)实现5日均线

1
2
3
sma52 = np.convolve( closing_prices, np.ones(5) / 5, 'valid')
mp.plot(dates[4:], sma52, c='limegreen', alpha=0.5,
linewidth=6, label='SMA-5(2)')

使用卷积函数numpy.convolve(a, b, 卷积类型)实现10日均线

1
2
sma10 = np.convolve(closing_prices, np.ones(10) / 10, 'valid')
mp.plot(dates[9:], sma10, c='dodgerblue', label='SMA-10')

使用卷积函数numpy.convolve(a, b, 卷积类型)实现加权5日均线

1
2
3
4
5
weights = np.exp(np.linspace(-1, 0, 5))
weights /= weights.sum()
ema5 = np.convolve(closing_prices, weights[::-1], 'valid')
mp.plot(dates[4:], sma52, c='limegreen', alpha=0.5,
linewidth=6, label='SMA-5')

布林带

布林带由三条线组成:

中轨:移动平均线

上轨:中轨+2x5日收盘价标准差 (顶部的压力)

下轨:中轨-2x5日收盘价标准差 (底部的支撑力)

布林带收窄代表稳定的趋势,布林带张开代表有较大的波动空间的趋势。

绘制5日均线的布林带

1
2
3
4
5
6
7
8
9
10
11
12
13
14
weights = np.exp(np.linspace(-1, 0, 5))
weights /= weights.sum()
em5 = np.convolve(closing_prices, weights[::-1], 'valid')
stds = np.zeros(em5.size)
for i in range(stds.size):
stds[i] = closing_prices[i:i + 5].std()
stds *= 2
lowers = medios - stds
uppers = medios + stds

mp.plot(dates, closing_prices, c='lightgray', label='Closing Price')
mp.plot(dates[4:], medios, c='dodgerblue', label='Medio')
mp.plot(dates[4:], lowers, c='limegreen', label='Lower')
mp.plot(dates[4:], uppers, c='orangered', label='Upper')