├── 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 | --------------------------------------------------------------------------------