Python中针对股票和期权投资组合的面向对象设计

7
我是一名初、中级Python程序员,但我只写过脚本,没有写过应用程序。目前我并不使用很多面向对象的设计,所以我希望这个项目能帮助我建立我的OOD技能。问题是,从设计角度出发,我不知道从哪里开始(我知道如何创建对象和所有这些东西)。值得一提的是,我也是自学的,没有正式的计算机科学教育。
我想尝试编写一个程序来跟踪投资组合的股票/期权持仓情况。
我有一个大致的想法,关于哪些对象会是良好的候选项(投资组合、股票、期权等)以及方法(买入、卖出、更新数据等)。
一个多头头寸会买入开仓,卖出平仓,而空头头寸则是卖出开仓,买入平仓。
portfolio.PlaceOrder(type="BUY", symbol="ABC", date="01/02/2009", price=50.00, qty=100)
portfolio.PlaceOrder(type="SELL", symbol="ABC", date="12/31/2009", price=100.00, qty=25)
portfolio.PlaceOrder(type="SELLSHORT", symbol="XYZ", date="1/2/2009", price=30.00, qty=50)
portfolio.PlaceOrder(type="BUY", symbol="XYZ", date="2/1/2009", price=10.00, qty=50)

那么,一旦调用了这个方法,我该如何存储信息?起初,我认为我会有一个Position对象,其属性如Symbol、OpenDate、OpenPrice等。但是考虑到更新持仓以考虑销售变得棘手,因为购买和销售发生在不同的时间和数量。

  • 购买100股开盘价,1次,1价格。4次不同的价格出售。
  • 购买100股。每天卖1股,共100天。
  • 4次不同的价格购买。以1次价格全部卖出。

可能的解决方案是为每一股股票创建一个对象,这样每一股股票都会有不同的日期和价格。这会产生太多的开销吗?投资组合可能会有数千或数百万个小股票对象。如果您想找出一个头寸的总市值,您需要像这样的东西:

sum([trade.last_price for trade in portfolio.positions if trade.symbol == "ABC"])

如果您有一个位置对象,计算就很简单:
position.last * position.qty

感谢您的帮助。从其他帖子中看来,显然SO是为“提供帮助”而不是“为您编写程序”。我认为我只需要一些指导,指引正确的方向。
附加信息: 目的: 该程序将跟踪所有头寸,包括开放和关闭;能够查看详细的盈利和亏损情况。
当我思考详细的P&L时,我想看到... - 所有的开放日期(和关闭日期) - 持有时间 - 开盘价(关闭日期) - 自开仓以来的盈亏 - 每日盈亏
@Senderle
我认为你可能过于字面地理解了“对象”这个比喻,所以试图把股票(在某些方面似乎很像对象)变成编程意义下的对象。如果是这样,那就是一个错误,这就是 juxtapose 的观点。
这是我的失误。考虑到“对象”,一个“股票”对象似乎是自然候选人。直到可能有数百万,这个想法才显得疯狂。我将在本周末有一些空闲的编码时间,并尝试创建一个带有数量的对象。

1
你想要为这种应用程序添加一个数据库层。 - Santa
出于好奇,我不太熟悉这个术语,我的理解是,“开放”头寸指的是在特定股票中持有的非零(正数或负数用于空头头寸)的数量,我的理解正确吗? - senderle
是的,空头头寸将是非零的。基本上有两种类型的头寸:多头和空头。当投资时,大多数人都想到多头。低买高卖。空头头寸则与多头相反。您从经纪人那里借股票并在市场上卖出它们,创建一项负债,您必须将股票归还给您的经纪人(或者当他要求您归还股票时)。整个过程是(借入股票)高价卖出,低价买入(把它们还给您的经纪人)。 - Jason Wirth
1
我知道这篇文章很老了,但我非常想知道你在这方面是否有任何进展,因为我正在思考同样的问题,而你比我早了2年。 - Bertie
5个回答

2
设计此类系统时应牢记两个基本原则:
  1. 从数据中消除冗余。无冗余可确保数据完整性。
  2. 将所有需要回答任何查询的数据保留在最低级别的详细程度上。
基于这些原则,我的建议是维护一个交易日志文件。每笔交易代表某种状态的变化,以及与之相关的所有重要事实:时间、买入/卖出、数量、价格等。每个交易都应以一个记录(使用namedtuple很有用)的形式表示在一个平面文件中。一年甚至五到十年的交易记录可以轻松容纳在内存中的列表中。然后,您可以创建函数来选择、排序和汇总您需要从此列表中获取的任何信息,并且由于它是内存驻留的,因此速度非常快,比SQL数据库快得多。
如果交易日志变得太大或太慢,您可以计算出特定日期(例如年末)所有持仓的状态,将其用作以下期间的初始状态,并将旧的日志文件归档到磁盘上。
您可能希望了解有关您持有股票的某些辅助信息,例如任何特定日期的价值/价格,以便您可以为任何或所有持股绘制价值与时间的图表(有在线信息源可提供此类信息,例如雅虎财经)。包含每个持股的静态信息的主数据库也会很有用。
我知道这听起来不太“面向对象”,但OO设计可以用于在一个TransLog对象中隐藏系统的详细工作方式,该对象具有将数据保存/恢复到/从磁盘(save/open方法)、输入/更改/删除交易以及其他将数据处理成有意义的信息显示的方法。
首先编写带有命令行界面的API。当满足您的要求时,您可以继续创建GUI前端。
祝你好运并玩得开心!

1
回答你的问题:你似乎已经对你的数据模型有了相当清晰的想法。但是在我看来,你需要更多地考虑一下你希望这个程序做什么。它会跟踪股票价格的变化吗?下订单或建议下订单?还是只是跟踪你已经下的订单?每种用途可能需要不同的策略。
话虽如此,我不明白为什么你需要为每一股都创建一个对象;我不理解这种策略背后的原因。即使你想要能够详细跟踪你的订单历史,你也可以只存储聚合数据,比如“在日期z,以每股y美元的价格购买了x股”。
更有意义的做法是创建一个position对象(或者按照Hugh的术语,创建一个holding对象)——每支股票一个,如果你真的需要该股票持有情况的详细历史记录,则可以添加一个.order_history属性。而且,对于这种情况,数据库肯定会很有用。

稍微深入思考一下:我认为你可能太过于字面理解“对象”这个比喻,因此试图将一个在某些方面看起来非常像对象的共享变量转化为编程意义上的对象。如果是这样,那么这是一个错误,这也是我认为juxtapose所指出的问题。

我不同意他关于面向对象设计有缺陷的观点——这是一个相当大胆的声明!但他的回答在某种程度上是正确的,因为“对象”(又称类实例)与模块几乎完全相同**。它是一组相关函数,链接到一些共享状态。在类实例中,状态通过selfthis共享,而在模块中,则通过全局命名空间共享。

对于大多数情况而言,类实例和模块之间唯一的主要区别在于可以有许多类实例,每个实例都有自己独立的状态,而只能有一个模块实例。(当然还有其他区别,但大多数时候涉及到的是不太重要的技术问题,对于学习面向对象设计并不是很重要。)这意味着您可以像思考模块一样思考对象,在这里这是一种有用的方法。

在许多编译语言中,当你编译一个模块时生成的文件被称为“对象”文件。我认为这就是“对象”比喻实际来自哪里。(我没有任何真正的证据!所以任何知道得更好的人,可以随意纠正我。)我们经常看到的面向对象设计的玩具例子,如car.drive(mph=50); car.stop(); car.refuel(unleaded, regular),我认为它们是一些回推出的形式,可能会让概念有点混淆。


谢谢,你的评论很有帮助。我同意过于字面思考问题的观点。 - Jason Wirth

1
避免使用对象。面向对象设计存在缺陷。将程序视为对数据(列表和字典)执行的行为集合,然后将相关行为作为模块中的函数分组。 每个函数都应具有清晰的输入和输出。在每个模块中全局存储数据。 为什么不使用对象?因为它更接近问题空间。面向对象编程会创建过多的间接性来解决问题。不必要的间接性会导致软件膨胀和错误。
为每个股票份额创建一个对象可能是一种可能的解决方案,这样每个股票份额就会有不同的日期和价格。这是否会产生太多开销?投资组合可能有数千万个小股票对象。如果您想找出某个头寸的总市值,您需要类似以下的东西:
是的,这会产生太多开销。解决方案是将数据存储在数据库中。除非使用NOSQL方案,否则计算头寸的总市值将在SQL中完成。
不要尝试为所有可能的未来结果进行设计。只需使您的程序按照当前需要工作即可。

4
我同意“将数据存储在数据库中”和“不要为所有事情过度设计”,但我非常不同意“面向对象设计有缺陷”。做得不好是可能的,但任何东西都可能如此。 - Hugh Bothwell
也许我不知道如何正确地做它。但我喜欢创造东西,而不是考虑如何创造它们。 - eat_a_lemon
@juxtapose:真的吗?你有多少个超过500行的程序是不经思考就写出来的? - Hugh Bothwell
@Hugh Heh。这不是我的意思。我对面向对象的经验是,我必须穿过一堆环来解决问题。此外,程序往往更复杂,因为我添加了不必要的抽象。具有一组函数的模块总是能完成工作。也许我还没有开发过真正大而复杂的软件……但我不确定我是否想要这样做。 - eat_a_lemon
我基本上同意Hugh的评论。@juxtapose:我喜欢让“问题空间”确定解决方案的想法,而一组函数可能是OOD解决方案的替代方案。 - Jason Wirth
我简直不敢相信我是第一个点赞这篇文章的人。我完全同意@eat_a_lemon的观点。我会更进一步地说,#unixphilosophy就是答案。 - Sridhar Sarnobat

1

我认为我会将其分成以下几个部分:

  • 持仓(每个符号您当前拥有或欠下的数量)
  • 订单(简单的购买或出售单个符号的需求)
  • 交易(订单的集合)

这使得获取当前价值、排队订单和构建更复杂的订单变得非常容易,并且可以轻松地映射到具有数据库支持的数据对象中。


我正在设想这个如何运作。很明显,价格历史应该存储在数据库中。但是股票怎么办?我是否仍然希望将一个对象视为一份股票?(只是在这种情况下,它将成为数据库记录而不是对象) - Jason Wirth

0

我很想听听你的想法。我已经花了大约4个月(兼职)来创建一个订单处理程序,虽然它基本上已经完成,但我仍然有与你一样的问题,因为我希望它能够被正确地制作。

目前,我保存两个文件

一个是"策略命中日志",其中保存了来自任何策略脚本的每个买入/卖出信号。例如:当触发buy_at_yesterdays_close_price.py策略时,它会将该买入请求保存在此文件中,并将请求传递给订单处理程序。

一个是"订单日志",它是一个单独的DataFrame - 此文件符合您关注的目的。

  • 每个策略的请求都涉及单个基础证券(例如AAPL股票),并创建一个订单,该订单作为包含Ticker、生成此Order的策略名称以及SuborderBroker Suborder列的行保存在DataFrame中(下面解释)。
  • 每个订单都有一个存储在Suborder列中的子订单(字典)列表。例如:如果您看好AAPL,则子订单可能是:
[
{'security': 'equity', 'quantity':10},
{'security': 'option call', 'quantity':10}
]

每个订单还有一个存储在Broker Suborder列中的经纪人子订单(字典)列表。每个Broker Suborder都是向经纪人发出购买或出售某种证券的请求,并使用经纪人为该请求提供的“订单ID”进行索引。每次向经纪人提出新请求都会生成一个新的Broker Suborder,取消该Broker Suborder会记录在该字典中。要记录对Broker Suborders的修改,您需要取消旧的Broker Suborder并发送并记录一个新的Broker Suborder(使用IBKR相同的佣金)。

改进

  1. 使用类列表而不是DataFrame:我认为将每个订单保存为 Order_Class 的实例(而不是 DataFrame 的一行)会更符合 Pythonic,该实例具有 SuborderBroker_Suborder 属性,两者都是 Suborder_ClassBroker_Suborder_Class 的实例。我的问题是,将类列表作为所有开放和关闭订单的记录是否符合 Pythonic 或愚蠢。

  2. 可视化考虑:似乎应该以表格形式保存订单以便于查看,但也许最好将它们保存在这种“类实例列表”形式中,并在查看时使用函数将其制表化?任何人的意见都将不胜感激。我想开始尝试 ML,但我不想留下未完成的订单处理程序。

  3. 这全是废话吗?:每个 Broker Suborder(向经纪人发出的买入/卖出请求)是否应附加到一个 Order(仅是来自策略脚本的特定策略请求)上,还是所有 Broker Suborders 都按时间顺序记录,并简单地引用生成 Broker Suborder 的策略订单(Order)?我不知道...但我很想听听大家的意见。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接