金程AQF

Python量化随笔丨那些年,我写Python踩过的坑(一)

       这是一篇关于Python量化的笔记~坑的时候有一种很奇怪的感觉,我对python似曾相识,又觉得陌生。打游戏发现新的隐藏关卡带来的是喜悦,而python的坑带来的是自我怀疑,甚至可能是实实在在的经济损失,所以,坑,我们踩过的,一定带你绕过去。


  下面让我们一起看看python隐藏的坑,排名不分先后。 >>>点击咨询量化投资行业前景


  1. Python量化之基础篇


  基础的坑,最重要的是举一反三,最怕的是在黑板上老师写了‘一’,回家在笔记本上写一个横线就不认识这种事情。


  忘记写冒号。在def,if,while,for等语句第一行某位输入”:”。


  缩进要一致。避免在缩进时混合使用制表符和空格。


  不要认为在原处修改对象的函数会返回结果。例如a = list.append(),a是不会被赋值的。


  不要在变量赋值前就使用变量。例如a = b写这行代码时,b在此之前一定要被赋值过的。


  函数调用一定要加括号。例如dataframe.dropna()。


  不要写死循环。例如while True: 之后的代码块中没有跳出循环的代码。


  判断两个变量是否相等用的是‘==’号。大多数初学者,觉得‘=’号就可以了。


  写字符串,写各种样式的括号时注意他们是成对出现的。常见['a', b']这样的错误,然后觉得自己代码没有问题的初学者。


  >>>点击咨询如何系统学习Python量化投资


  2. Python量化之进阶篇


  有一句歌词唱的挺好的,“跨过这道山,越过那道岭,眼前又是一座峰”。基础的坑排完了,还会有更多,更隐藏的坑等着你去踩。(比较熟悉这个歌的同学,我觉得你大概率上是北方人,更具体点的话,是东北人)


  2.1 赋值仅仅生成引用


  a = [1, 2, 3]


  b = a


  这里你认为自己构建了两个相同的列表,准备用在不同的用途。然而当开开心心的进行接下来的其他操作时,比如,对a进行修改,就发生了其他的事情。


  a[1] = 100000a


  out: [1, 100000, 3]


  b


  out: [1, 100000, 3]


  结果发现,本来希望只对a进行修改,结果发现b也受到了影响,其原因在于通过b = a的方式进行对b赋值,其实a、b指向同一个列表对象。我们可以通过查看a、b的内存地址或用is进行验证


  print(id(a), id(b))


  out: 124006856 124006856


  可以看到a、b指向的其实是同一块内存


  a is b


  out: True


  用is也检测出a和b完全就是一个东西的不同名字而已。


  上面的赋值方法还是比较容易看出,因为有等号,那么下面的赋值方法可能就稍微难一点看出来了。


  c = [1, a, 3]


  c


  out: [1, [1, 100000, 3], 3]


  当对a修改时,c同样也会受到影响


  a.append(100000)


  a


  out: [1, 100000, 3, 100000]


  c


  out: [1, [1, 100000, 3, 100000], 3]


  所以,不要觉得写完了,print出来的东西看着和自己想的一样就万事大吉,不实际踩一下肯定不知道接下来有坑。


  那么,如何解决呢?用 .copy(),这样就会产生两个不相干的对象,当然如果不嫌麻烦的话,可以把相同的东西再打一遍,然后,你有没有看到同行鄙视的眼神?


  我们看一下效果。


  a = [1, 2, 3]


  b = a.copy()


  a is b


  out: False


  print(id(a), id(b))


  out:125323528 87389000


  可以看到a,b指向了不同的内存地址,并且用is检测显示是不同对象。


  接下来修改a。


  a[1] = 100000a


  out: [1, 100000, 3]


  b


  out: [1, 2, 3]


  可以看到修改a已经不会对b产生影响了,此坑已填。


  2.2 乘法与嵌套列表


  编程的某个时候,你希望生成这样一个嵌套列表[[],[],[],..., [],[]],里面的列表为空或者默认值,那么第一选择肯定是利用列表乘法一次性生成多个列表,像这样


  a = [[]] * 5a


  out: [[], [], [], [], []]


  确实满足了需求,然后当你开开心心的使用时,发觉事情有点不太对。比如对a列表中作为元素的某一个列表进行修改


  a[0].append(1000)


  a[0]


  out: [1000]


  a


  out: [[1000], [1000], [1000], [1000], [1000]]


  然后发现,怎么所有作为元素的列表全都发生了变化。这次同样可以用id或这is进行检测。


  print(id(a[0]), id(a[1]))


  out: 125325256 125325256


  a[0] is a[1]


  out: True


  可以看出,原来a列表中作为元素的每一个列表,其实都是同一个东西,这也就解释了为什么对其中一个作为元素的列表进行原地修改时,其他所有作为元素的列表也发生了变化。


  那么,解决方案如下,


  a = [[] for i in range(5)]


  a


  out: [[], [], [], [], []]


  a[0] is a[1]


  out: False


  print(id(a[0]), id(a[1]))


  out: 125323144 125321544


  可以看到,a中作为元素的列表已经不是同一个了,这样对其中的列表进行修改时候就不会影响其他列表。


  a[0].append(1000)


  a


  out: [[1000], [], [], [], []]


  此坑已填


  2.3 本地变量静态检测


  刚刚了解作用域的同学应该对LEGB原则有一定了解,然后实践中可能大胆的写出了这样的函数。


  a = 1def print_a():


  print(a)


  a = 2


  这个函数的目的也比较容易理解,打印a的值之后将a的值修改为2,但是,实际运行时发生了这样的事情。


  print_a()


  ---------------------------------------------------------------------------


  out: UnboundLocalError Traceback (most recent call last)


  <ipython-input-24-4bb72463237f> in <module>()


  ----> 1 print_a()


  <ipython-input-23-feb3b9a58246> in print_a()


  1 a = 1


  2 def print_a():


  ----> 3 print(a)


  4 a = 2


  UnboundLocalError: local variable 'a' referenced before assignment


  发生这样问题的原因是在python读入并编译这段代码时,发现def里面有一个对a赋值的语句,然后决定a在函数中属于本地变量名。那么当print_a执行时,就会对print(a)按照LEGB原则执行搜索,发现a属于本地变量名但还未赋值。也就是我们在前面基础坑里面提到的在变量未赋值前进行使用。


  解决方案需要使用global声明a是一个全局变量。


  a = 1def print_a():


  global a


  print(a)


  a = 2


  print_a()


  out: 1


  a


  out: 2


  可以看到,函数已经可以正常使用,并且全局变量a按照预期进行了修改。此坑已填。


  2.4 可变对象作函数默认参数


  默认参数在def语句运行时完成了保存,对于可变对象而言,当函数被调用并对该参数进行原地修改,默认参数将发生变化并进而影响接下来的函数调用。


  先用可变对象作为默认参数编写一个函数。


  def test(a=[]):


  a.append(1)


  print(a)


  接下来多次调用test函数,你一定以为每次打出来的都是一个包含1的列表。但是,事实并不是这样。


  test()


  out: [1]


  test()


  out: [1, 1]


  test()


  out: [1, 1, 1]


  是不是感觉三观尽毁啊。


  当然,有坑就会有填坑的方案。官方给出的标准解决方案是这样的。


  def test(a=None):


  if a is None:


  a = []


  a.append(1)


  print(a)


  test()


  out: [1]


  test()


  out: [1]


  可以看到,test多次被调用已经不会出现之前的情况了。此坑已填。


  2.5 嵌套在循环中的lambda


  在知道python中一切皆对象之后,也许你就会尝试把一些特别的东西放进一个list,虽然不清楚未来实际会不会用到这样的写法,反正先试试。然后你就想将一套简单的函数放进一个列表,然后用lambda编写。


  func_list = []for i in range(5):


  func_list.append(lambda x: x + i)


  这样生成了一个元素为函数的列表,其中每个函数都可以传入一个参数,然后返回的是传入值和0,1,2,3,4的和。当然理想情况下是这样,现实并不会如此。


  func_list[0](1)


  out: 5


  func_list[1](1)


  out: 5


  func_list[2](1)


  out: 5


  可以发现,所有结果都是4+1的和。发生这种现象的原因在于变量i在函数调用时才进行查找,而此时循环已经结束,i=4,所以所有函数对象在被调用时参数i全部都为5.


  那么解决方案就是按照本文2.4中写的,默认参数在def语句运行时完成了保存,也就是使用默认参数的方式解决该问题。


  func_list = []for i in range(5):


  func_list.append(lambda x, i=i: x + i)


  func_list[0](1)


  out: 1


  func_list[1](1)


  out: 2


  func_list[2](1)


  out: 3


  可以看到,问题已经得到解决。此坑已填。


  2.6 列表和enumerate


  熟悉列表,学习python算是入门;熟悉enumerate,学习python可以说不是纯小白了。enumerate返回下标和元素的方式还是为python使用者提供了不大不小的便利,不过同时也挖下了一个坑。


  比如你写了一个循环,想从列表中删除偶数。


  a = [1,2,3,4,5,6]for i, item in enumerate(a): if a[i] % 2==0: del a[i]


  a


  out: [1, 3, 5]


  是不是想说没什么错误啊,然后在给你看看这个。


  a = [1,2,6,3,4,4]for i, item in enumerate(a): if a[i] % 2==0: del a[i]


  a


  out: [1, 6, 3, 4]


  enumerate(a)


  out: <enumerate at 0x78e6d80>


  产生这个问题的原因是在当i=1时,a[1]的值为2,符合条件,那么删除2这个值之后,a整个列表发生了变化,6这个数值前进一个位置到了a[1]的位置,然后i在执行下一次循环时候值取了2,我们以为应该对应着检验6,其实6被前移一个位置,也就被遗漏了。


  下面我们将for 循环的部分手动执行,看现实是不是和我们理解的一样。


  a = [1,2,6,3,4,4]


  e = enumerate(a)


  e


  out: <enumerate at 0x78edc18>


  i, item = e.__next__()


  (i, item)


  out: (0, 1)


  if a[i] % 2==0: del a[i]


  a


  out: [1, 2, 6, 3, 4, 4]


  此时我们可以看到,执行完循环的第一步,a并没有发生变化。接下来我们继续第二步。


  i, item = e.__next__()


  (i, item)


  out: (1, 2)


  if a[i] % 2==0: del a[i]


  a


  out: [1, 6, 3, 4, 4]


  可以发现,a已经发生了变化,那么我们接下来只要看在enumerate提供新的下标好元素是不是还按照未调整的a进行提供的。当然,可以告诉你们答案,肯定不是了。


  i, item = e.__next__()


  (i, item)


  out: (2, 3)


  可以看到6已经不是第三个元素, 它通过向前移动一个位置的方式逃过了检查。这个坑真的是非常难发现,因为有时候碰巧你的运算方式和提供的列表刚刚好结果是你想要的,然后让你觉得这样用就是正确的。这种时候就非常可怕了。


  这个坑的解决方案可以使用列表解析式添加筛选条件即可。


  a = [1, 2, 6, 3, 4, 4]


  a = [x for x in a if x%2 != 0]


  a


  [1, 3]


  此坑已填。


  是不是觉得觉得一不留神就掉进Python的坑里了?哈哈哈,写到这里,python编程一些比较基础的坑也已经描述的比较详细了。我从来不产生失落感,我只是信心的粉碎机。当你觉得自己python已经学的差不多的时候,总会有那么一个人、或者一篇文章告诉你,你懂得还不够多。


  不过,坑总是有的,学习的时候你可以抱着填坑的心态,也可以怀着获取的目的,两者都取决于你的选择。而且,个人觉的,怀着获取的态度,容易满足,不断追求,也就一直快乐。


  最后,还是用毒翼神龙的一段话结个尾:“几盘卡带就可以陪我们走过整个童年的那个时代恐怕也一去不复返了吧。每天的生活被工作、学习、应酬充斥着,也让我们渐渐忘记了那个传说~虽然它是一段美丽的谣言,但想起那个信以为真的年纪,仍然有许多美好的回忆。我怀念那时单纯的快乐,怀念那个很容易就能满足的童心”


       量化金融分析师(简称AQF ,Analyst of Quantitative Finance)由量化金融标准委员会(Standard Committee of Quantitative Finance,SCQF)主考并颁证,是代表量化金融领域的专业水平证书。 >>>点击咨询AQF证书含金量


  


       课程适合人群:


  金融工程/数学专业背景的同学/工作人士,希望进一步学习Python编程以及在量化投资的实战应用;

  非金融工程专业背景的同学/工作人士,希望迅速成为宽客;

  金融相关人员,希望学习如何系统的做量化策略;

  个人投资者,希望系统学习掌握量化投资相关的实务技能,从模型开发,回测,策略改进,搭建稳定的量化交易系统。


>>>点击咨询AQF课程相关问题



  量化金融分析师AQF核心课程体系:

  1、《量化投资基础》

  主要涵盖了量化投资领域的必备知识,包括:基本面分析、技术分析、数量分析、固定收益、资产组合管理、权益、另类投资等内容。

  2、《Python语言编程基础》

  包含了Python环境搭建、基础语法、变量类型、基本函数、基本语句、第三方库、金融财务实例等内容。旨在为金融财经人提供最需要的编程方法。

  3、《基于Python的经典量化投资策略》

  包含了最富盛名,最基本的量化交易思想和交易策略。例如:海龟交易模型、Logistics模型、配对交易模型、波动扩张模型、Alpha模型、机器学习(随机森林模型、主成分分析)、深度学习(人工神经网络)等内容。

  4、《量化交易系统设计》

  旨在学习量化交易系统的具体知识,包括过滤器,进入信号,退出信号,仓位管理等详细内容,并指导学员设计涵盖个人交易哲学的量化交易系统。

  5、《量化实盘交易》

  旨在为解决实际量化交易策略搭建过程中的一些问题提供最优解决方案。  >>>点击咨询AQF相关问题


  掌握Python及量化投资技能,我们能做什么?


       1、熟悉中国主要金融市场及交易产品的交易机制;


       2、熟知国内外期货交易、股市交易的异同点和内在运行机制;


       3、掌握经典量化交易策略细节及其背后的交易哲学;


       4、掌握金融、编程和建模知识基础,拥有量化交易实盘操作能力;


       5、具备独立自主地研发新量化交易策略的能力;


       6、掌握量化交易模型设计的基本框架,以及风险管理和资产组合理论的实际运用;


       7、掌握从策略思想——策略编写——策略实现饿完整量化投资决策过程;具备量化投资实战交易能力。


      咨询电话:400-700-9596

      AQF考友群:760229148

  金融宽客交流群:801860357

  微信公众号:量化金融分析师


评论