├── TestHistory
└── __init__.py
├── NetTrade
├── Notes
│ ├── __init__.py
│ └── RealNotes.py
├── Util
│ ├── __init__.py
│ └── dateUtil.py
├── HistoryNotes
│ ├── __init__.py
│ └── HistoryNotes.py
├── Strategy
│ ├── __init__.py
│ └── NetstrategyA.py
├── TestDataUtil
│ ├── __init__.py
│ ├── DataGetter.py
│ ├── GetterFactory.py
│ └── JSLGetter.py
├── Variables
│ ├── __init__.py
│ └── Status.py
├── ExcelDataUtil
│ ├── __init__.py
│ ├── xlsxDataWriter.py
│ ├── xlsxDataGetter.py
│ └── headers.py
└── __init__.py
├── .gitignore
├── requirements.txt
├── testHistory.py
├── finacial.py
├── rename_scripts.py
├── test.py
├── pyproject.toml
├── LICENSE
└── README.md
/TestHistory/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/NetTrade/Notes/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/NetTrade/Util/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/NetTrade/HistoryNotes/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/NetTrade/Strategy/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/NetTrade/TestDataUtil/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/NetTrade/Variables/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/NetTrade/ExcelDataUtil/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.xlsx
2 | *idea*
3 | *.pyc
4 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests
2 | idataapi-transform
3 |
--------------------------------------------------------------------------------
/NetTrade/__init__.py:
--------------------------------------------------------------------------------
1 | """NetTrade tools"""
2 |
3 | __version__ = '1.0.3'
4 |
--------------------------------------------------------------------------------
/NetTrade/Variables/Status.py:
--------------------------------------------------------------------------------
1 |
2 | class Status(object):
3 | BUY = "1"
4 | SELL = "2"
5 |
6 | CN_MAP = {
7 | BUY: "买入",
8 | SELL: "卖出"
9 | }
10 |
--------------------------------------------------------------------------------
/testHistory.py:
--------------------------------------------------------------------------------
1 | from NetTrade.HistoryNotes.HistoryNotes import HistoryNotes
2 | from NetTrade.Strategy.NetstrategyA import NetstrategyA
3 |
4 | if __name__ == "__main__":
5 | HistoryNotes(NetstrategyA, "jsl", "sz162411", 0.6, 2000, 2017, 2019, range_percent=0.03, growth_rate=0.3)
6 |
--------------------------------------------------------------------------------
/finacial.py:
--------------------------------------------------------------------------------
1 |
2 | def calculate(mon_per_year, rate, target):
3 | init_year = 0
4 | init_mon = 0
5 | while init_mon < target:
6 | init_mon += mon_per_year
7 | init_mon *= 1 + rate
8 | init_year += 1
9 | print("year: %d, mon: %f" % (init_year, init_mon))
10 |
11 |
12 | calculate(20, 0.2, 1000)
13 |
--------------------------------------------------------------------------------
/NetTrade/TestDataUtil/DataGetter.py:
--------------------------------------------------------------------------------
1 | import abc
2 |
3 | class DataGetter(object):
4 | @abc.abstractmethod
5 | def get_data(self, stock_code, date):
6 | """
7 | :param stock_code: e.g sz162411
8 | :param date: e.g 2018
9 | :return: [(datetime1, value1), (datetime2, value2)]
10 | """
11 | pass
12 |
--------------------------------------------------------------------------------
/rename_scripts.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | base_path = ""
5 | old_name2new_name_map = dict()
6 | for each in os.listdir(base_path):
7 | new_file_name = "意难忘_" + re.search("\d+", each).group(1) + ".flv"
8 | old_name2new_name_map[each] = new_file_name
9 |
10 | for k, v in old_name2new_name_map.items():
11 | print(k, v)
12 |
13 |
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 | from NetTrade.Notes.RealNotes import RealNotes
2 | from NetTrade.Strategy.NetstrategyA import NetstrategyA
3 |
4 | def note():
5 | r = RealNotes("sz162411", NetstrategyA, range_percent=0.03, growth_rate=0.3)
6 | # r.buy(0.505, 4000)
7 | r.calc_next_val()
8 | r.pr_status()
9 |
10 |
11 | if __name__ == "__main__":
12 | note()
13 |
--------------------------------------------------------------------------------
/NetTrade/TestDataUtil/GetterFactory.py:
--------------------------------------------------------------------------------
1 | from .JSLGetter import JSLGetter
2 |
3 | getter_map = {
4 | "jsl": JSLGetter
5 | }
6 |
7 | class GetterFactory(object):
8 | def create_getter(self, source="jsl"):
9 | if source not in getter_map:
10 | raise NotImplementedError("date getter: %s not Implemented" % (source, ))
11 | return getter_map[source]()
12 |
--------------------------------------------------------------------------------
/NetTrade/ExcelDataUtil/xlsxDataWriter.py:
--------------------------------------------------------------------------------
1 | from idataapi_transform import ProcessFactory, WriterConfig
2 | from .headers import Headers
3 |
4 | class XlsxDataWriter(object):
5 | @staticmethod
6 | def write_data(file_name, items):
7 | with ProcessFactory.create_writer(WriterConfig.WXLSXConfig(file_name, headers=Headers.fields_cn_order, filter_=Headers.filter_en2cn)) as writer:
8 | writer.write(items)
9 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires=["flit"]
3 | build-backend="flit.buildapi"
4 |
5 | [tool.flit.metadata]
6 | module="NetTrade"
7 | author="zpoint"
8 | author-email="zp@zp0int.com"
9 | home-page="https://github.com/zpoint/NetTrade"
10 | classifiers=["License :: OSI Approved :: MIT License"]
11 | requires=["idataapi-transform", "requests"]
12 | requires-python=">=3.5.2"
13 | keywords="NetTrade eft拯救世界 网格策略 网格"
14 | dist-name="NetTrade"
15 |
--------------------------------------------------------------------------------
/NetTrade/Util/dateUtil.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | def timestamp2datetime(value):
4 | """
5 | timestamp2datetime(1471234567) -> "2016-1-1 12:12:12"
6 | """
7 | struct_time = time.localtime(value)
8 | dt = time.strftime('%Y-%m-%d %H:%M:%S', struct_time)
9 | return dt
10 |
11 | def datetime2timestamp(dt):
12 | """
13 | datetime2timestamp("2016-1-1 12:12:12") -> 1471234567
14 | """
15 | struct_time = time.strptime(dt, "%Y-%m-%d %H:%M:%S")
16 | return int(time.mktime(struct_time))
17 |
--------------------------------------------------------------------------------
/NetTrade/ExcelDataUtil/xlsxDataGetter.py:
--------------------------------------------------------------------------------
1 | from idataapi_transform import ProcessFactory, GetterConfig
2 | from .headers import Headers
3 | from .xlsxDataWriter import XlsxDataWriter
4 | import os
5 |
6 | class XlsxDataGetter(object):
7 | @staticmethod
8 | def get_data(file_name, raise_if_not_exist=True):
9 | ret_list = list()
10 |
11 | if not os.path.exists(file_name):
12 | if raise_if_not_exist:
13 | raise ValueError("您还未进行过任何操作,请至少记录一次操作(买入/卖出),再进行查看/计算")
14 | else:
15 | return ret_list
16 |
17 | getter = ProcessFactory.create_getter(GetterConfig.RXLSXConfig(file_name, filter_=Headers.filter_cn2en))
18 | for items in getter:
19 | ret_list.extend(items)
20 | return ret_list
21 |
--------------------------------------------------------------------------------
/NetTrade/ExcelDataUtil/headers.py:
--------------------------------------------------------------------------------
1 | class Headers(object):
2 | fields_en2cn_map = {
3 | "value": "净值",
4 | "shares": "份额",
5 | "money": "金额",
6 | "date_str": "日期",
7 | "status": "状态",
8 | "timestamp": "时间戳"
9 | }
10 | fields_cn2en_map = {v: k for k, v in fields_en2cn_map.items()}
11 | fields_en_order = ["value", "shares", "money", "date_str", "status", "timestamp"]
12 | fields_cn_order = list()
13 | for i in fields_en_order:
14 | fields_cn_order.append(fields_en2cn_map[i])
15 |
16 | @staticmethod
17 | def filter_en2cn(item):
18 | new_item = {k: v for k, v in zip(Headers.fields_cn_order, item)}
19 | return new_item
20 |
21 | @staticmethod
22 | def filter_cn2en(item):
23 | for float_keys in ("value", "shares", "money", "timestamp"):
24 | item[Headers.fields_en2cn_map[float_keys]] = round(float(item[Headers.fields_en2cn_map[float_keys]]), 3)
25 | r = tuple(item[k] for k in Headers.fields_cn_order)
26 | return r
27 |
--------------------------------------------------------------------------------
/NetTrade/TestDataUtil/JSLGetter.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import logging
3 | from datetime import datetime
4 | from idataapi_transform import ProcessFactory # for log
5 | from .DataGetter import DataGetter
6 |
7 | class JSLGetter(DataGetter):
8 | def get_data(self, stock_code, date):
9 | """
10 | :param stock_code: e.g sz162411
11 | :param date: e.g 2018
12 | :return: [(datetime1, value1), (datetime2, value2)]
13 | """
14 | url = "http://data.gtimg.cn/flashdata/hushen/daily/%s/%s.js" % (str(date)[2:], stock_code)
15 | logging.info("requesting %s" % (url, ))
16 | r = requests.get(url)
17 | if '
404' in r.text:
18 | raise ValueError("无法从以下 url 获取到历史数据,请确保参数均填写正确: %s" % (url, ))
19 | ret_data = list()
20 | for line in r.text.split("\n")[1:-1]:
21 | line_lst = line.split(" ")
22 | date_obj = datetime.strptime(line_lst[0], "%y%m%d")
23 | val = float(line_lst[-2])
24 | ret_data.append((date_obj, val))
25 | return ret_data
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Guo Zpoint
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NetTrade/Notes/RealNotes.py:
--------------------------------------------------------------------------------
1 | import time
2 | from ..ExcelDataUtil.xlsxDataGetter import XlsxDataGetter
3 | from ..ExcelDataUtil.xlsxDataWriter import XlsxDataWriter
4 | from ..Util.dateUtil import timestamp2datetime
5 | from ..Variables.Status import Status
6 |
7 |
8 | class RealNotes(object):
9 | def __init__(self, code, strategy, **kwargs):
10 | self.file_name = code + ".xlsx"
11 | self.code = code
12 | self.strategy = strategy
13 | self.operation_history = None
14 | self.kwargs = kwargs
15 |
16 | def pr_status(self):
17 | if self.operation_history is None:
18 | self.init_strategy()
19 | self.strategy.print_status()
20 |
21 | def calc_next_val(self):
22 | if self.operation_history is None:
23 | self.init_strategy()
24 | tup_buy, tup_sell = self.strategy.calc_next_buy_sell_val()
25 | buy_value, buy_shares, buy_money = tup_buy
26 | print("\n\n下次买入价格: %-8s\t买入份额: %-8s\t买入金额: %-8s" % (buy_value, buy_shares, buy_money))
27 | if tup_sell:
28 | sell_value, sell_shares, sell_money = tup_sell
29 | print("下次卖出价格: %-8s\t卖出份额: %-8s\t卖出金额: %-8s\n\n" % (sell_value, sell_shares, sell_money))
30 | else:
31 | print("份额已卖完,无下次卖出价格\n")
32 |
33 | def buy(self, value, shares, ts=None):
34 | operation_history = XlsxDataGetter.get_data(self.file_name, raise_if_not_exist=False)
35 | # "value", "shares", "money", "date_str", "status", "timestamp"]
36 | if shares % 100 != 0:
37 | raise ValueError("请输入100的整数倍份额")
38 | money = shares * value
39 | if ts is None:
40 | ts = int(time.time())
41 | operation_history.append((value, shares, money, timestamp2datetime(ts), Status.BUY, ts))
42 | XlsxDataWriter.write_data(self.file_name, operation_history)
43 |
44 | def sell(self, value, shares, ts=None):
45 | operation_history = XlsxDataGetter.get_data(self.file_name)
46 | if ts is None:
47 | ts = int(time.time())
48 | operation_history.append((value, shares, value * shares, timestamp2datetime(ts), Status.SELL, ts))
49 | XlsxDataWriter.write_data(self.file_name, operation_history)
50 |
51 | def init_strategy(self):
52 | self.operation_history = XlsxDataGetter.get_data(self.file_name)
53 | self.strategy = self.strategy(self.operation_history, **self.kwargs)
54 |
55 | def calc_curr_val(self, value):
56 | if self.operation_history is None:
57 | self.init_strategy()
58 | tup_buy, tup_sell = self.strategy.calc_curr_buy_sell_val(value)
59 | if tup_buy is not None:
60 | buy_value, buy_shares, buy_money = tup_buy
61 | print("\n当前净值跌幅过大,需要加大买入, 价格: %-8s\t买入份额: %-8s\t买入金额: %-8s" % (buy_value, buy_shares, buy_money))
62 | elif tup_sell is not None:
63 | sell_value, sell_shares, sell_money = tup_sell
64 | print("当前净值涨幅过大: 需要加大卖出,价格: %-8s\t卖出份额: %-8s\t卖出金额: %-8s\n" % (sell_value, sell_shares, sell_money))
65 | else:
66 | print("\n当前净值波动在合理范围内\n")
67 |
--------------------------------------------------------------------------------
/NetTrade/HistoryNotes/HistoryNotes.py:
--------------------------------------------------------------------------------
1 | import math
2 | from datetime import datetime
3 | from ..TestDataUtil.GetterFactory import GetterFactory
4 | from ..Variables.Status import Status
5 |
6 |
7 | class HistoryNotes(object):
8 | def __init__(self, strategy, source, code, begin_val, net_price, year_begin, year_end, log_for_every_round=False, **kwargs):
9 | """
10 | :param source: 数据来源
11 | :param code: 交易代码
12 | :param begin_val: 入网净值
13 | :param net_price: 初始价格
14 | :param year_begin: 开始的年份
15 | :param year_end: 结束的年份
16 | :param log_for_every_round: 每当第一网卖出后,清空重新计算
17 | """
18 | getter = GetterFactory.create_getter(source)
19 | date_now = datetime.now()
20 | year_now = date_now.year
21 | if year_end > year_now:
22 | year_end = year_now
23 | result_data = list()
24 | for year in range(year_begin, year_end+1):
25 | ret_data = getter.get_data(code, year)
26 | result_data.extend(ret_data)
27 |
28 | self.strategy = strategy
29 | self.code = code
30 | self.begin_val = begin_val
31 | self.net_price = net_price
32 | self.log_for_every_round = log_for_every_round
33 | self.kwargs = kwargs
34 | self.operation_history = list()
35 | self.curr_strategy = None
36 | self.next_buy_value, self.next_buy_shares, self.next_buy_money, self.next_sell_value, self.next_sell_shares, self.next_sell_money = None, None, None, None, None, None
37 | for date_time, value in result_data:
38 | if not self.next_buy_value:
39 | # first net
40 | if value <= begin_val:
41 | self.buy(value, date_time, True)
42 | else:
43 | if value <= self.next_buy_value:
44 | self.buy(value, date_time)
45 | elif self.next_sell_value is not None and value >= self.next_sell_value:
46 | self.sell(value, date_time)
47 | if self.curr_strategy is not None:
48 | self.log_and_clear()
49 |
50 | def buy(self, value, date_time, first_time=False):
51 | if first_time:
52 | share = self.net_price / self.begin_val
53 | share = math.ceil(share / 100) * 100
54 | money = share * value
55 | else:
56 | # recalculate next buy money if needed
57 | if value < self.next_buy_value:
58 | buy_tup, sell_tup = self.curr_strategy.calc_curr_buy_sell_val(value)
59 | self.next_buy_value, self.next_buy_shares, self.next_buy_money = buy_tup
60 | money = self.next_buy_money
61 |
62 | self.operation_history.append((value, round(money / value, 3), money, date_time.strftime("%Y-%m-%d %H:%M:%S"), Status.BUY, int(date_time.timestamp())))
63 | if first_time:
64 | self.curr_strategy = self.strategy(self.operation_history, **self.kwargs) if self.kwargs else self.strategy(self.operation_history)
65 | else:
66 | self.curr_strategy.re_static()
67 | self.recalculate_next()
68 |
69 | def sell(self, value, date_time):
70 | if value > self.next_sell_value:
71 | buy_tup, sell_tup = self.curr_strategy.calc_curr_buy_sell_val(value)
72 | self.next_sell_value, self.next_sell_shares, self.next_sell_money = sell_tup
73 |
74 | self.operation_history.append((value, self.next_sell_shares, round(value * self.next_sell_shares, 3), date_time.strftime("%Y-%m-%d %H:%M:%S"), Status.SELL, int(date_time.timestamp())))
75 | self.curr_strategy.re_static()
76 | self.recalculate_next()
77 | if value > self.operation_history[0][0] and self.log_for_every_round:
78 | self.log_and_clear()
79 |
80 | def recalculate_next(self):
81 | next_buy_bup, next_sell_tup = self.curr_strategy.calc_next_buy_sell_val()
82 | self.next_buy_value, self.next_buy_shares, self.next_buy_money = next_buy_bup
83 | if next_sell_tup:
84 | self.next_sell_value, self.next_sell_shares, self.next_sell_money = next_sell_tup
85 | else:
86 | self.next_sell_value, self.next_sell_shares, self.next_sell_money = None, None, None
87 |
88 | def log_and_clear(self):
89 | print("结束一轮网格: ")
90 | self.curr_strategy.print_status()
91 | self.operation_history = list()
92 | self.curr_strategy = None
93 | self.next_buy_value, self.next_buy_shares, self.next_buy_money, self.next_sell_value, self.next_sell_shares, self.next_sell_money = None, None, None, None, None, None
94 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NetTrade
2 | 网格交易策略辅助记录脚本
3 | 本脚本只是本人个人针对 ETF拯救世界 在以下地方提到过的闲钱网格的探索测试以及记录,完全是本人的个人行为
4 |
5 | 策略来源:
6 | * [闲散资金的策略-微博问答 2017-03-22](https://weibo.com/ttwenda/p/show?id=2310684088171439759396)
7 | * [网格说明-雪球 2015年9月2日](https://xueqiu.com/4776750571/55799950)
8 | * [有没有博友愿意为大家做道题? 2013年8月23日](https://www.chinaetfs.net/?p=895)
9 | * [网格操作记录 2012年6月25日](https://www.chinaetfs.net/?p=757)
10 |
11 | 数据来源:
12 | * [集思录分享](https://www.jisilu.cn/question/55996)
13 | * [螺丝钉分享](https://xueqiu.com/1997857856/62838103)
14 |
15 | 计算方法:
16 | * [估值计算方法](http://fund.eastmoney.com/news/1594,20170322722385868.html)
17 |
18 |
19 | ### 注意
20 | * 你可以理解成 E大 提出了算法,本人用自己的理解把算法实现了一遍,同样的算法不同的实现,可能存在不同的边界判定,运行效率,甚至出现逻辑错误,本人尽最大努力追求易用性和 bug-free
21 | * 测试时用每日收盘净值净值测试,数据源来自上面的集思录分享,假设最多每日只能操作一次
22 | * 网格策略只适合波动市,单边下跌或者单边上涨不适用,如下第一网时不留足安全边际,很可能非常长一段时间收不回网(下的位置不够低,导致几年都回不到这个位置)
23 | * 本项目还在开发中,具体进度视本人时间安排而定
24 |
25 | ### 功能
26 | * 自定义网格策略
27 | * 用历史数据进行策略收益计算
28 | * 对自定义网格策略下一次入网,出网价格,当前收益率等值计算,程序自动保存至 **excel**,可也手动编辑 **excel** 修改记录后程序读入
29 |
30 | ### 策略概述
31 |
32 | * 条件A: 低于历史估值均值 20% (每个人计算估值的方法应该是不太一样的,作为小韭菜也没有获取数据的来源,你可以对接自己的数据来源, 这里作为测试先写死一个净值开始)
33 | * 条件B: 还没想好
34 |
35 | #### 策略A
36 | 1. 第一网: 达到条件(A或B或者其他,自己定), 入 1 网
37 | 2. 幅度
38 | * a: 3%
39 | * b: 5%
40 | 3. 下跌: 每达到最近次交易价格的幅度, 每网金额增加 20%,入一网
41 | 4. 上涨: 每达到最近次交易价格的幅度, 每网金额减小 20%, 出网
42 | * 5a: 将低于当前价格买入的份额全部出掉
43 | * 5b: 买入份额/买入次数 得到一个平均每次买入份数,出掉低于当前价格的买入次数 * 平均每次买入份数
44 |
45 | #### 策略B
46 | n = 入网次数-出网次数 (n = 0)
47 | 1. 第一网: 达到条件, 入 1 网 (n = 1)
48 | 2. 幅度
49 | * a: 3%
50 | * b: 5%
51 | 3. 下跌: 每达到最近次交易价格的幅度, 买入 money 金额,money = max((floor(n / 3) + 1) * 每网钱数, 你能接受的一网的最大买入的钱数), 总买入次数 += floor(money / 每网初始价格)
52 | 4. 上涨: 每达到最近次交易价格的幅度, 出网
53 | * 5a: 将低于当前价格买入的份额全部出掉
54 | * 5b: 买入份额/买入次数 得到一个平均每次买入份数,出掉低于当前价格的买入次数 * 平均每次买入份数
55 |
56 | #### 怎么玩?
57 |
58 | ##### 环境/安装
59 |
60 | # python >= 3.5.2
61 | pip3 install NetTrade
62 |
63 | ##### 进行网格记录
64 |
65 | from NetTrade.Notes.RealNotes import RealNotes
66 | from NetTrade.Strategy.NetstrategyA import NetstrategyA
67 |
68 | if __name__ == "__main__":
69 | # 以华宝油气为例, 传入华宝油气代码,第一次调用 buy, 会在当前目录下生成同名 xlsx 文件,用于记录和下次读取计算
70 | # 目前只实现了策略A基本版
71 | # range_percent: 选填,网格幅度,默认 3%
72 | # growth_rate: 选填,每下一网增加金额,默认 20%, 这里传的是 30%
73 | r = RealNotes("sz162411", NetstrategyA, range_percent=0.03, growth_rate=0.3)
74 | # 写入购买记录,净值为 0.505, 份额为4000份
75 | r.buy(0.505, 4000)
76 | r.calc_next_val() # 计算并打印下次需要购买和卖出的份额与净值
77 | # 下次买入价格: 0.49 买入份额: 5400 买入金额: 2646.0
78 | # 下次卖出价格: 0.521 卖出份额: 4000.0 卖出金额: 2084.0
79 |
80 | # 写入购买记录,净值为 0.49, 份额为 5400
81 | # r.buy(0.49, 5400)
82 | # 写入购买记录,净值为 0.475, 份额为 7300
83 | # r.buy(0.475, 7300)
84 | # 写入卖出记录,净值为 0.521,份额为 4000
85 | # r.sell(0.521, 4000)
86 | # 计算下一网的买入和卖出的净值和份额,并打印
87 | # r.calc_next_val()
88 | # 计算网格操作至今各种数据
89 | r.pr_status()
90 |
91 |
92 | ##### 测试策略历史收益
93 |
94 | from NetTrade.HistoryNotes.HistoryNotes import HistoryNotes
95 | from NetTrade.Strategy.NetstrategyA import NetstrategyA
96 |
97 | if __name__ == "__main__":
98 | # 目前只实现了策略A: NetstrategyA
99 | # 目前只有来自集思录的数据源分享: jsl
100 | # 测试代码华宝油气: sz162411
101 | # 开始入第一网的净值: 0.6
102 | # 第一网的价格为 2000 (会根据100的整数倍调整)
103 | # 2017 表示从2017年开始(闭区间)
104 | # 2019 表示到2019奶奶结束(闭区间)
105 | # range_percent 下跌/上涨幅度 这里是 3%
106 | # growth_rate 每下一网价格上涨幅度 这里是 30%
107 | HistoryNotes(NetstrategyA, "jsl", "sz162411", 0.6, 2000, 2017, 2019, range_percent=0.03, growth_rate=0.3)
108 |
109 |
110 | ##### 单日过大跌幅/涨幅记录
111 |
112 | from NetTrade.Notes.RealNotes import RealNotes
113 | from NetTrade.Strategy.NetstrategyA import NetstrategyA
114 |
115 | def note():
116 | r = RealNotes("sz162411", NetstrategyA, range_percent=0.03, growth_rate=0.3)
117 | r.buy(0.505, 4000)
118 | r.buy(0.49, 5400)
119 | r.buy(0.475, 7300)
120 | r.calc_curr_val(0.505)
121 | # 当前净值涨幅过大: 需要加大卖出,价格: 0.505 卖出份额: 12700.0 卖出金额: 6413.5
122 | r.sell(0.505, 12700)
123 | r.calc_curr_val(0.455)
124 | # 当前净值跌幅过大,需要加大买入, 价格: 0.455 买入份额: 11100 买入金额: 5050.5
125 | # r.calc_next_val()
126 |
127 | # r.pr_status()
128 | # r.calc_curr_val(0.505)
129 |
130 |
131 | if __name__ == "__main__":
132 | note()
133 |
134 |
135 |
136 |
137 | ##### 自定义策略
138 | * 待开发
139 |
140 |
141 | #### 输出示例
142 |
143 | r.calc_next_val()
144 | # 下次买入价格: 0.461 买入份额: 9800 买入金额: 4517.8
145 | # 下次卖出价格: 0.49 卖出份额: 7300.0 卖出金额: 3577.0
146 |
147 | r.pr_status()
148 | # 操作历史:
149 | # 净值 份额 金额 操作 日期 当前动用过本金 当前收益率
150 | # 0.505 4000.0 2020.0 买入 2019-03-06 16:39:09 2020.0 0.00%
151 | # 0.49 5400.0 2646.0 买入 2019-03-06 16:39:09 4666.0 -1.29%
152 | # 0.475 7300.0 3467.5 买入 2019-03-06 16:39:09 8133.5 -2.47%
153 | # 0.505 12700.0 6413.5 卖出 2019-03-06 16:40:33 8133.5 3.69%
154 | #
155 | # 当前投入的钱数 (还未卖出的钱数的和): 2020.0
156 | # 当前持有份额: 4000.0
157 | # 投入部分当前市值: 2020.0
158 | # 总的使用过的本金(当前投入的本金 + 卖出的本金): 8133.5
159 | # 总的当前的资产(投入部分当前市值 + 卖出部分获得的金额): 8433.5
160 | # 当前总收益: 300.00
161 | # 当前收益率: 3.69%
162 |
--------------------------------------------------------------------------------
/NetTrade/Strategy/NetstrategyA.py:
--------------------------------------------------------------------------------
1 | import time
2 | import math
3 | import logging
4 | from ..Variables.Status import Status
5 |
6 |
7 | class NetstrategyA(object):
8 | def __init__(self, operate_history, range_percent=0.03, growth_rate=0.2):
9 | """
10 | :param operate_history: [(value1, shares, money1, date_str, status, timestamp), (value2, shares, money2, date_str, status, timestamp) ... ]
11 | :param range_percent: 幅度, e.g 0.03
12 | :param growth_rate: 每网增加幅度 0.2
13 | """
14 | self.operate_history = operate_history
15 | self.buy_history_including_sold, self.sell_history, self.buy_history, \
16 | self.curr_buy_money, self.sum_shares, self.curr_shares_worth, \
17 | self.total_base_money, self.total_current_money, self.curr_val = self.split_history()
18 | self.init_val = self.operate_history[0][0]
19 | self.range_percent = range_percent
20 | self.growth_rate = growth_rate
21 |
22 | def re_static(self):
23 | self.buy_history_including_sold, self.sell_history, self.buy_history, \
24 | self.curr_buy_money, self.sum_shares, self.curr_shares_worth, \
25 | self.total_base_money, self.total_current_money, self.curr_val = self.split_history()
26 | self.init_val = self.operate_history[0][0]
27 |
28 | def calc_next_buy_sell_val(self):
29 | """
30 | :return: ((buy_value, buy_shares, buy_money), (sell_value, sell_shares, sell_money))
31 | """
32 | if not self.buy_history:
33 | if self.operate_history:
34 | buy_tup = (self.operate_history[0][0], self.operate_history[0][1], self.operate_history[0][2])
35 | else:
36 | buy_tup = None
37 | return buy_tup, None
38 |
39 | latest_buy_value, latest_buy_money = self.buy_history[-1][0], self.buy_history[-1][2]
40 |
41 | next_fall_value = round(latest_buy_value * (1 - self.range_percent), 3)
42 | next_fall_money = latest_buy_money * (1 + self.growth_rate)
43 | next_fall_shares = int(math.ceil(next_fall_money / next_fall_value / 100) * 100) # 取100的整数份额
44 | next_fall_money = next_fall_value * next_fall_shares
45 |
46 | next_grow_value = latest_buy_value * (1 + self.range_percent)
47 | r = math.modf(next_grow_value * 1000)
48 | if r[0]:
49 | next_grow_value = (r[1] + 1) / 1000
50 | else:
51 | next_grow_value = r[1] / 1000
52 | next_grow_shares = 0
53 | need_sell_history_index_including = self.bin_search(next_grow_value)
54 | for each in self.buy_history[need_sell_history_index_including:]:
55 | next_grow_shares += each[1]
56 | next_grow_money = next_grow_shares * next_grow_value
57 |
58 | return (round(next_fall_value, 4), round(next_fall_shares, 4), round(next_fall_money, 4)), \
59 | (round(next_grow_value, 4), round(next_grow_shares, 4), round(next_grow_money, 4))
60 |
61 | def calc_curr_buy_sell_val(self, curr_val):
62 | tup1, tup2 = self.calc_next_buy_sell_val()
63 | next_buy_value, next_buy_shares, next_buy_money = tup1
64 | if tup2:
65 | next_sell_value, next_sell_shares, next_sell_money = tup2
66 | else:
67 | next_sell_value, next_sell_shares, next_sell_money = None, None, None
68 |
69 | if next_sell_value is not None and curr_val > next_sell_value:
70 | # there are some shares need to be sold, but currently hold
71 | need_sell_history_index_including = self.bin_search(curr_val)
72 | next_grow_shares = 0
73 | for each in self.buy_history[need_sell_history_index_including:]:
74 | next_grow_shares += each[1]
75 | next_grow_money = next_grow_shares * curr_val
76 | return None, (curr_val, next_grow_shares, round(next_grow_money, 4))
77 | elif curr_val < next_buy_value:
78 | # need to buy more, price is lower than curr_val
79 | operation = self.buy_history[-1] if self.buy_history else self.operate_history[0]
80 | x = math.log(curr_val / operation[0], 1-self.range_percent)
81 | next_fall_money = operation[2] * math.pow(1 + self.growth_rate, x)
82 | next_fall_shares = int(math.ceil(next_fall_money / curr_val / 100) * 100) # 取100的整数份额
83 | next_fall_money = curr_val * next_fall_shares
84 | return (curr_val, next_fall_shares, round(next_fall_money, 4)), None
85 | return None, None
86 |
87 | def bin_search(self, value, begin=None, end=None):
88 | if begin is None:
89 | begin = 0
90 | if end is None:
91 | end = len(self.buy_history) - 1
92 |
93 | if begin == end: # terminate
94 | if value > self.buy_history[begin][0]:
95 | return begin
96 | return end + 1 if end != len(self.buy_history) - 1 else None
97 |
98 | middle = int((begin + end) / 2)
99 | if value == self.buy_history[middle][0]:
100 | # best match, 不能卖出相同净值的买入份额
101 | return self.bin_search(value, middle, middle)
102 | elif value < self.buy_history[middle][0]:
103 | return self.bin_search(value, middle+1, end)
104 | elif value > self.buy_history[middle][0]:
105 | return self.bin_search(value, begin, middle)
106 |
107 | def split_history(self):
108 | buy_history_including_sold = list()
109 | sell_history = list()
110 | buy_history = list()
111 | curr_buy_money = 0 # 当前投入的钱数 (还未卖出的钱数的和)
112 | sum_shares = 0 # 当前持有份额
113 | curr_shares_worth = 0 # 投入部分当前市值(净值为 curr_val)
114 | total_current_money = 0 # 总的当前的钱数(投入部分当前市值 + 卖出部分获得的金额)
115 | already_sold_money = 0 # 总的卖出获得的金额
116 |
117 | curr_used_money = 0 # 当前占用本金
118 | curr_not_used_money = 0 # 当前卖出未占用本金
119 | # 总收益率 = total_current_money / total_base_money
120 | for each in self.operate_history:
121 | if each[4] == Status.BUY:
122 | new_curr_not_used_money = curr_not_used_money
123 | curr_used_money += each[2]
124 | new_curr_not_used_money -= each[2]
125 | if new_curr_not_used_money < 0:
126 | new_curr_not_used_money = 0
127 | already_sold_money -= curr_not_used_money
128 | else:
129 | already_sold_money -= each[2]
130 | curr_not_used_money = new_curr_not_used_money
131 |
132 | buy_history_including_sold.append(each)
133 | buy_history.append(each)
134 | elif each[4] == Status.SELL:
135 | sell_history.append(each)
136 | max_index = len(buy_history)-1
137 | if len(buy_history) == 1:
138 | need_sell_history = buy_history
139 | max_index = -1
140 | else:
141 | sum_shares = 0
142 | for i in range(max_index, -1, -1):
143 | max_index = i
144 | if buy_history[max_index][0] >= each[0] or sum_shares >= each[1]:
145 | break
146 | sum_shares += buy_history[max_index][1]
147 | need_sell_history = buy_history[max_index + 1:]
148 | sold_rest_money = sum(i[2] for i in need_sell_history)
149 | curr_used_money -= sold_rest_money
150 | curr_not_used_money += sold_rest_money
151 | already_sold_money += each[0] * sum(i[1] for i in need_sell_history)
152 | buy_history = buy_history[:max_index+1]
153 | else:
154 | raise ValueError("Unknown status: %s" % (str(each), ))
155 |
156 | sum_shares = 0
157 | for i in buy_history:
158 | curr_buy_money += i[2]
159 | sum_shares += i[1]
160 | curr_val = self.operate_history[-1][0]
161 | curr_shares_worth = sum_shares * curr_val
162 | total_current_money = round(curr_shares_worth + already_sold_money, 2)
163 | return buy_history_including_sold, sell_history, buy_history, curr_buy_money, \
164 | sum_shares, curr_shares_worth, round(curr_used_money + curr_not_used_money, 3), total_current_money, curr_val
165 |
166 | def print_status(self):
167 | print("操作历史:")
168 | if not self.operate_history:
169 | print("无任何操作记录")
170 | else:
171 | buy_history = list()
172 | curr_used_money = 0 # 当前占用本金
173 | curr_not_used_money = 0 # 当前卖出未占用本金
174 | already_sold_money = 0
175 | print("%-10s\t%-10s\t%-10s\t%-10s\t%-20s\t%-15s\t%-10s" % ("净值", "份额", "金额", "操作", "日期", "当前动用过本金", "当前收益率"))
176 | for each in self.operate_history:
177 | total_current_money = 0 # 总的当前的钱数(投入部分当前市值 + 卖出部分获得的金额)
178 | if each[4] == Status.BUY:
179 | new_curr_not_used_money = curr_not_used_money
180 | curr_used_money += each[2]
181 | new_curr_not_used_money -= each[2]
182 | if new_curr_not_used_money < 0:
183 | new_curr_not_used_money = 0
184 | already_sold_money -= curr_not_used_money
185 | else:
186 | already_sold_money -= each[2]
187 | curr_not_used_money = new_curr_not_used_money
188 | buy_history.append(each)
189 | else:
190 | if len(buy_history) == 1:
191 | need_sell_history = buy_history
192 | max_index = -1
193 | else:
194 | max_index = len(buy_history) - 1
195 | for i in range(max_index, -1, -1):
196 | max_index = i
197 | if buy_history[max_index][0] >= each[0]:
198 | break
199 | need_sell_history = buy_history[max_index + 1:]
200 |
201 | sold_rest_money = sum(i[2] for i in need_sell_history)
202 | curr_used_money -= sold_rest_money
203 | curr_not_used_money += sold_rest_money
204 | buy_history = buy_history[:max_index + 1]
205 | already_sold_money += each[0] * sum(i[1] for i in need_sell_history)
206 | sum_shares = sum(i[1] for i in buy_history)
207 | total_current_money += sum_shares * each[0] + already_sold_money
208 | total_used_base_money = curr_used_money + curr_not_used_money
209 | rate = (total_current_money - total_used_base_money) / total_used_base_money
210 | print("%-10s\t%-10s\t%-10s\t%-10s\t%-20s\t%-20s\t%-10s" %
211 | (str(each[0]), str(each[1]), str(each[2]), Status.CN_MAP[str(each[4])], each[3],
212 | round(total_used_base_money, 3), "%.2f%%" % (rate * 100, )))
213 |
214 | print("\n%s" % ("当前投入的钱数 (还未卖出的钱数的和): ", ), round(self.curr_buy_money, 3))
215 | print("%s" % ("当前持有份额: ", ), self.sum_shares)
216 | print("%s" % ("投入部分当前市值: ", ), round(self.curr_shares_worth, 3))
217 | print("%s" % ("总的使用过的本金(当前投入的本金 + 卖出的本金): ", ), self.total_base_money)
218 | print("%s" % ("总的当前的资产(投入部分当前市值 + 卖出部分获得的金额): ", ), self.total_current_money)
219 | rate = (self.total_current_money - self.total_base_money) / self.total_base_money
220 | print("%s" % ("当前总收益: %.2f " % (self.total_current_money - self.total_base_money, )))
221 | print("%s" % ("当前收益率: %.2f%% " % (rate * 100)), )
222 | ts_begin = int(self.operate_history[0][5])
223 | ts_now = int(self.operate_history[-1][5])
224 | days_interval = (ts_now - ts_begin) / (24 * 3600)
225 | years_interval = days_interval / 365
226 | if years_interval < 1:
227 | average_year_rate = (365 * rate / days_interval) if days_interval else 0
228 | else:
229 | if rate < 0:
230 | sign = -1
231 | else:
232 | sign = 1
233 | average_year_rate = sign * math.pow(abs(rate), 1 / years_interval)
234 | print("\n%s" % ("第一份到最后份时长: %.2f 天, 平均年化: %.2f%% " % (days_interval, average_year_rate * 100)), )
235 |
--------------------------------------------------------------------------------