使用python+mongodb实现网格交易的思路:
网格,顾名思义,就是低买高卖,实现方案是用python,对某只股票的历史数据:
- 从x天开始算(当天就买入)
- 设定一个预期卖出价格,比如,比上次买入价格盈利5%,则卖出
- 设定一个预期买入价格,比如,比上次买入价格下跌5%,再次买入
由于还需要考虑其他问题,比如:
- 买入的股数,必须是100的整数倍
- 当天买入的份额,当天是不能卖的
- 如果股价一飞冲天,会不会卖飞?为了解决这个问题,我们使用一个数值:“累计买入次数”,买入+1,卖出-1,只需要观察数值的大小,就知道最大会卖飞多少,那么对应的,考虑为其建底仓。
- 股价会不会一直下跌?理论上是会的,我们还是使用“累计买入次数”这个变量,来看极限情况下,最多买入多少次(考虑了卖出-1)
系列文章:
- 策略1:使用python+mongodb实现股票网格交易代码----附python源码
- 使用matplotlib画k线(2条k线同列)----附python源码
- 策略3:动态再平衡----附python源代码
标的选择
这个是很多的,比如我常看的是下面这几个,比较稳健:
数据抓取
抓取历史数据,有很多的方案,比如
- Tushare:https://www.tushare.pro/
- 万得api
- 爬虫(我用过搜狐证券:https://q.stock.sohu.com/cn/512010/lshq.shtml)
网格策略
变量描述
方案跟最开始的描述一致,代码中用到的一些变量
self.collection_detail_name = '策略2-网格-明细'+security
self.collection_summary_name = '策略2-网格-汇总'+security
super().__init__()
#################### 计算数据 ########################
self.symbol = security
self.collection_name = security
self.mongo_util = MongoUtil()
self.start_date = start_date
################## 策略过程数据 #######################
self.last_buy_price = 0.000
# 预期买入价格
self.expect_buy_price = 0.000
# 预期卖出价格(根据最近一次买入价格计算) self.last_buy_price*(1+self.expect_percent)
self.expect_sell_price = 0.000
# 最多加仓多少次
self.buy_count = 0
self.sell_count = 0
# 最大浮亏金额
self.lose_total_money = 0.000
# 最大浮亏比例。 比如4.323%,这里就是4.323,不需要%。由于收益率是最终数据,此数据记录中间过程
self.lose_percent = 0.000
# 持有时间(从买到卖总天数,非交易天数,是自然日)
self.hold_days = 0
##################### 持仓情况 ########################
# cost: 成本价; capital:本金;shares:持有份额;benefit:收益;benefit_rate:收益率(不包含%)
# transactions: 交易信息 {'date': '', 'money': 0.000, 'shares':0, 'direction': '买入或卖出'}
self.hold_meta = {'cost': 0.000, 'capital': 0.000, 'shares': 0, 'benefit': 0.000, 'benefit_rate': 0.000,
'transactions': []}
##################### 达标目标 ########################
# 预期达标比例,例如盈利10%,就是0.1
self.expect_percent = 0.05
# 每次买入金额(这个还需要根据实际买入份额计算100整数倍)
self.per_buy_capital = 10000
开始测算
计算的时候,只需要如下:
# security是股票代码,date是希望从这一天开始测算,理论上随便选取哪天都可以,这样的话,可以策略在高点回落的极端场景,比如2018年以及之后的走势
strategy_meta = {"security": "SH601939", 'date': '2018-01-02'}
this = StrategyGrid(security=strategy_meta['security'], start_date=strategy_meta['date'])
# 删除mongodb中的旧的测算数据
this.delete_collection()
# 开始测算
this.run()
结果分析
这个是重头戏,先贴上来测算的结果(如有不对,麻烦指出)
标题头部:
_id:mongodb的唯一键
Benefit:利润,赚的钱
Benefit_rate:利润率
Buy_count: 前文解释过,是统计的历史"买入-卖出"次数
Capital:本金
cost:成本
date:交易日期(买入或卖出日期)
direction:交易方向(买入或卖出)
money:买入或卖出金额
price:买入或卖出价格
security:股票代码
Shares: 当前持仓数量
Start_date:测算的开始日期
-
从2018-01-02开始,总共交易了多少次?买入和卖出各多少?
共79次,买入40次,卖出39次
-
buy_count最大和最小多少?
buy_count代表的是最多买入多少笔(最多加仓次数),最多卖出多少笔(卖飞),还是比较重要的
-
最大:5
-
最小:-3
后续还会增加matplotlib进行可视化显示,比较好分析数据。
-
部分源码
import math
from Strategies.PercentCustom import PercentCustom
from utils.mongo_util.MongoUtil import MongoUtil
class StrategyGrid:
def __init__(self, security, start_date) -> None:
self.collection_detail_name = '策略2-网格-明细'+security
self.collection_summary_name = '策略2-网格-汇总'+security
super().__init__()
#################### 计算数据 ########################
self.symbol = security
self.collection_name = security
self.mongo_util = MongoUtil()
self.start_date = start_date
################## 策略过程数据 #######################
self.last_buy_price = 0.000
# 预期买入价格
self.expect_buy_price = 0.000
# 预期卖出价格(根据最近一次买入价格计算) self.last_buy_price*(1+self.expect_percent)
self.expect_sell_price = 0.000
# 最多加仓多少次
self.buy_count = 0
self.sell_count = 0
# 最大浮亏金额
self.lose_total_money = 0.000
# 最大浮亏比例。 比如4.323%,这里就是4.323,不需要%。由于收益率是最终数据,此数据记录中间过程
self.lose_percent = 0.000
# 持有时间(从买到卖总天数,非交易天数,是自然日)
self.hold_days = 0
##################### 持仓情况 ########################
# cost: 成本价; capital:本金;shares:持有份额;benefit:收益;benefit_rate:收益率(不包含%)
# transactions: 交易信息 {'date': '', 'money': 0.000, 'shares':0, 'direction': '买入或卖出'}
self.hold_meta = {'cost': 0.000, 'capital': 0.000, 'shares': 0, 'benefit': 0.000, 'benefit_rate': 0.000,
'transactions': []}
##################### 达标目标 ########################
# 预期达标比例,例如盈利10%,就是0.1
self.expect_percent = 0.05
# 每次买入金额(这个还需要根据实际买入份额计算100整数倍)
self.per_buy_capital = 10000
def delete_collection(self):
self.mongo_util.db[self.collection_detail_name].delete_many({})
def run(self):
start_record = self.mongo_util.db[self.collection_name].find_one({'date': self.start_date})
start_shares = this.cal_shares_by_price(start_record['close'])
start_money = start_shares * start_record['close']
this.buy(date=start_record['date'], money=start_money, shares=start_shares, price=start_record['close'])
# 下面这2个在this.buy已经计算过了,这里就不用再计算了
# self.next_buy_price = cal_next_buy_price(start_record['close'])
# self.expect_sell_price = this.cal_expect_sell_price(start_record['close'])
# 从MongoDB读取数据,按照日期升序,即由远及近,默认1为升序,-1为降序
# cursor = self.mongo_util.load_all_data(self.collection_name)
cursor = self.mongo_util.db[self.collection_name].find({"date": {"$gt": self.start_date}}).sort("date", 1)
for record in cursor:
print('当天数据\n' + str(record) + '\n')
print('预期买入价格' + str(self.expect_buy_price) + '\n')
# 遍历循环
# 持有时间(从买到卖总天数,非交易天数,是自然日)
self.hold_days += 1
# 因为由于可能下跌幅度较大,需要计算可买入的价格。
while True:
print('预期买入价格' + str(self.expect_buy_price))
if record['low'] <= self.expect_buy_price:
print('当天最低价' + str(record['low']))
# 此时按照 self.expect_buy_price 进行买入
shares = this.cal_shares_by_price(self.expect_buy_price)
money = shares * self.expect_buy_price
this.buy(date=record['date'], money=money, shares=shares, price=self.expect_buy_price)
# 买入后,期望买入价格变化(由于在this.buy已经计算了下一个买入价格,所以这里就不需要再次计算了,直接使用即可)
print('预期买入价格(重新计算)' + str(self.expect_buy_price))
else:
print('当天最低价' + str(record['low']) + ' > ' + str(self.expect_buy_price) + ',当天计算结束')
break
while True:
print('预期卖出价格' + str(self.expect_sell_price))
if record['high'] >= self.expect_sell_price:
print('当天最高价' + str(record['high']))
# 此时按照 self.expect_sell_price 进行卖出
shares = this.cal_shares_by_price(self.expect_sell_price)
money = shares * self.expect_sell_price
this.sell(date=record['date'], money=money, shares=shares, price=self.expect_sell_price)
# 卖出后,期望卖出价格变化(由于在this.sell已经计算了下一个卖出价格,所以这里就不需要再次计算了,直接使用即可)
print('预期卖出价格(重新计算)' + str(self.expect_sell_price))
else:
print('当天最高价' + str(record['high']) + ' < ' + str(self.expect_sell_price) + ',当天计算结束')
break
def cal_shares_by_price(self, current_price):
"""
总结:根据股价,计算买入的份额 = math.floor(1000/股价/100)*100
:param current_price:
:return:
"""
return math.ceil(self.per_buy_capital / current_price / 100) * 100
def buy(self, date, money, shares, price):
"""
执行买入动作,传入买入日期,买入的金额。买入时,会触发下面的数据计算:
1. "持仓情况"重新计算
2. "策略过程数据"更新
:param date: 交易日期,字符串
:param money: 本次买入金额
:param shares: 本次买入份额
:param price: 交易价格
:return:
"""
self.buy_count += 1
print('当前总买入次数' + str(self.buy_count))
# cost 成本价=(本次买入金额 + capital)/(shares + 本次买入份额)
self.hold_meta['cost'] = (money + self.hold_meta['capital']) / (self.hold_meta['shares'] + shares)
# capital 本金 = capital + 本次买入金额
self.hold_meta['capital'] = self.hold_meta['capital'] + money
# shares 持有份额 = shares + 本次买入份额
self.hold_meta['shares'] = self.hold_meta['shares'] + shares
# benefit 收益 = (当前价格 - 成本价) * 持有份额
self.hold_meta['benefit'] = (price - self.hold_meta['cost']) * self.hold_meta['shares']
# benefit_rate 收益率 = 收益/本金
self.hold_meta['benefit_rate'] = self.hold_meta['benefit']/self.hold_meta['capital']
# transactions 交易信息 {'date': '', 'money': 0.000, 'shares':0, 'direction': '买入或卖出'}
this_transaction = {'uuid': self.symbol + '|' + self.start_date + '|' + date + '|' + str(self.buy_count),
'buy_count': self.buy_count, 'security': self.symbol, 'start_date': self.start_date,
'date': date, 'money': money, 'direction': '买入', 'price':price,
'cost': self.hold_meta['cost'], 'capital': self.hold_meta['capital'], 'shares': self.hold_meta['shares'],
'benefit': self.hold_meta['benefit'], 'benefit_rate': self.hold_meta['benefit_rate']
}
self.hold_meta['transactions'].append(this_transaction)
# 将本次交易数据存储到MongoDB
self.mongo_util.save_or_update_by_uuid(mongodb_collection=self.collection_detail_name, data_dict=this_transaction)
# 预期买入价格重新计算
self.expect_buy_price = self.cal_expect_buy_price(price)
# 预期卖出价格
self.expect_sell_price = self.cal_expect_sell_price(price)
# 最大浮亏金额 (因为只有下跌才会买,所以,就按照当前价格和成本计算即可)
self.lose_total_money = self.hold_meta['benefit']
# 最大浮亏比例。 比如4.323%,这里就是4.323,不需要%。由于收益率是最终数据,此数据记录中间过程
self.lose_percent = self.hold_meta['benefit_rate']
def sell(self, date, money, shares, price):
"""
执行卖出动作,传入卖出日期,卖出的金额。卖出时,会触发下面的数据计算:
1. "持仓情况"重新计算
2. "策略过程数据"更新
:param date: 交易日期,字符串
:param money: 本次卖出金额
:param shares: 本次卖出份额
:param price: 交易价格
:return:
"""
self.sell_count += 1
self.buy_count -= 1
print('当前总卖出次数' + str(self.sell_count))
# cost 成本价=(capital - 本次卖出金额)/(shares - 本次卖出份额)
if (self.hold_meta['shares'] - shares) == 0:
self.hold_meta['cost'] = 0
else:
self.hold_meta['cost'] = (self.hold_meta['capital'] - money) / (self.hold_meta['shares'] - shares)
# capital 本金 = capital - 本次卖出金额
self.hold_meta['capital'] = self.hold_meta['capital'] - money
# shares 持有份额 = shares - 本次卖出份额
self.hold_meta['shares'] = self.hold_meta['shares'] - shares
# benefit 收益 = (当前价格 - 成本价) * 持有份额
self.hold_meta['benefit'] = (price - self.hold_meta['cost']) * self.hold_meta['shares']
# benefit_rate 收益率 = 收益/本金
if self.hold_meta['capital'] != 0:
self.hold_meta['benefit_rate'] = self.hold_meta['benefit'] / self.hold_meta['capital']
else:
self.hold_meta['benefit_rate'] = 0
# transactions 交易信息 {'date': '', 'money': 0.000, 'shares':0, 'direction': '买入或卖出'}
this_transaction = {'uuid': self.symbol + '|' + self.start_date + '|' + date + '|' + str(self.buy_count),
'buy_count': self.buy_count, 'security': self.symbol, 'start_date': self.start_date,
'date': date, 'money': money, 'direction': '卖出', 'price':price,
'cost': self.hold_meta['cost'], 'capital': self.hold_meta['capital'], 'shares': self.hold_meta['shares'],
'benefit': self.hold_meta['benefit'], 'benefit_rate': self.hold_meta['benefit_rate']
}
self.hold_meta['transactions'].append(this_transaction)
# 将本次交易数据存储到MongoDB
self.mongo_util.save_or_update_by_uuid(mongodb_collection=self.collection_detail_name, data_dict=this_transaction)
# 预期买入价格重新计算
self.expect_buy_price = self.cal_expect_buy_price(price)
# 预期卖出价格
self.expect_sell_price = self.cal_expect_sell_price(price)
# 最大浮亏金额 (因为只有下跌才会买,所以,就按照当前价格和成本计算即可)
self.lose_total_money = self.hold_meta['benefit']
# 最大浮亏比例。 比如4.323%,这里就是4.323,不需要%。由于收益率是最终数据,此数据记录中间过程
self.lose_percent = self.hold_meta['benefit_rate']
def cal_expect_sell_price(self, price):
"""
根据当前成本价,计算预期卖出价格 price*(1+self.expect_percent)
:return:
"""
return price * (1 + self.expect_percent)
def cal_expect_buy_price(self, price):
"""
根据当前成本价,计算预期买入价格 price*(1-self.expect_percent)
:return:
"""
return price * (1 - self.expect_percent)
def save_run_result(self, sell_record):
"""
保存单次策略运行结果。比如计算 2018-01-02 这一天开始的策略计算,sell_record就是2018-01-02这天买入后,在x那天卖出。
:param sell_record: 如果最终没有卖出,那么此为None
:return:
"""
sell_date = None
sold = False
earn_money = 0.000
if sell_record is not None:
sell_date = sell_record['date']
sold = True
earn_money = self.hold_meta['shares'] * (self.expect_sell_price - self.hold_meta['cost'])
result_record = {'uuid': self.symbol + '|' + self.start_date, "security": self.symbol, "date": sell_record['date'],
'加仓次数': self.buy_count, '最大浮亏': self.lose_total_money, '最大浮亏比例': self.lose_percent,
'持有天数': self.hold_days, '成本价': self.hold_meta['cost'], '总买入本金': self.hold_meta['capital'],
'总份额': self.hold_meta['shares'], '卖出日期': sell_date, '卖出价格': self.expect_sell_price,
'sold': sold,
'earn_money': earn_money
}
self.mongo_util.save_or_update_by_uuid(mongodb_collection=self.collection_summary_name, data_dict=result_record)
if __name__ == '__main__':
strategy_meta = {"security": "SH512000", 'date': '2018-01-02'}
this = StrategyGrid(security=strategy_meta['security'], start_date=strategy_meta['date'])
this.delete_collection()
this.run()