作者: Kevinwabbit

  • 使用集合和高级查询计算指数或个股相关度

    使用集合和高级查询计算指数或个股相关度

    前言

    近年来,各种互联网金融机构和基金公司一直都在教育散户投资者进行基金定时定额投资或定时不定额投资。关于基金定投的收益和风险不在本文的讨论之列,读者可自行在网上搜索。下面仅谈谈我个人关于基金定投(以下简称“定投”)的看法。

    无论互金们怎么把定投吹上天都不要相信,最重要的是自己掌握复盘的方法。对历史行情复盘后发现,按照定投的方法,无论在何时买入,到某个时间点总能实现盈利。所谓Any time is the best time——只要市场行情的整体趋势是向上的即可。因此定投是比较适合没时间盯行情,又打算拿出一笔钱来进行长期投资的职场人士。

    基金类型又分为很多种,依据定投的天然属性,越是波动剧烈的基金越是适合进行定投。所以,偏股类基金或者以某个指数作为投资标的的基金是最好的选择。同时,为了分散风险,即鸡蛋不放在同一个篮子里,可以同时选择多支指数/偏股基金进行定投。

    要做,就要做到最好。为了最大程度的分摊风险,所选的基金行情走向应该呈现不相关甚至是负相关的关系。因为,若是选择了两只相关的基金,同时大涨固然好。若是同时大跌,又选择了定期不定额的“慧定投”模式,可能在几天之内就会把“子弹”(计划用于投资的资金)打光。所以,当选择了两只不相关的基金,在一只赚钱到达止盈线之后可以进行收割,也有更充足的“弹药”应对另一只走低的基金。

    指定的两只基金的相关性还算是容易计算。抓取K线后复权的收盘价之后,用EXCEL里面的CORREL函数即可计算出来。几只基金两两之间的收盘价,辛苦一点也是可以扒拉出来的。但是几十只基金之间的呢?以及如何从中选择出相关性比较低的一种组合呢?

    目标

    我个人从去年就开始了第一笔定投,当时主要是结合股市常识选择了5个指数,哪些基金追踪的这些指数就不说了。上证50,代表大型公司。中证500,代表中型公司。创业板指,代表创业型公司。恒生国企指数,代表港股。纳斯达克指数,代表美股。它们之间很明显的没有互相包含关系,所以即使不用算也知道它们的平均相关性很低。但是现在正值股灾,我想在此基础上再多定投几只指数(基金),以此来调整个人的资产配置比例。适逢我人生哲学的其中一个信条就是:

    “重复的事情交给机器去做,解放出劳动力来进行更有意义的创造。”

    因而我编写了一个程序,专门用来计算多项数据之间的平均相关性,并从中挑选出相关性最低的组合。是的,它不仅仅可以用来计算指数或股票走势的相关性。任何多项数据都可以,只要每项数据的数据元素个数一致即可。因为本来计算相关性的公式就要求如此。

    步骤

    第一步,准备好数据源。

    我从沪深(中证)、上证、深证、港股、美股中挑选出了21个指数作为备选项。挑选的条件是能够从蚂蚁财富中买到相应的跟踪基金,并且可选的基金个数不少于两个。主题指数不参与计算,有兴趣的读者可以自行实现。然后从行情软件中下载好2017年10月16日到2018年10月16日的每日收盘价。

     

    第二步,进行数据清洗。

    按日期(按列)码齐每个指数是最基本的。然后就会发现A股(大陆)指数、港股指数、美股指数都会出现不连续的空隙。这是由于各个证交所放假导致交易日不同而导致的。对于这些数据,我采用了手工进行线性插值的方法处理。这部分数据对整体的影响不大。为了避免后面涉及到对XLS文件的繁琐编程,把该sheet数据单独保存为一个CSV文件。这样即可在编程的时候使用普通的文件流读取方法把数据载入内存中。

    第三步,建立验证数据。

    由于是第一次进行该类编程,还需要有一个参考标准来对比验证程序的计算结果。因此我又在EXCEL中利用CORREL公式计算了出所有21个指数两两之间的相关度。

    第四步,编程。

    4.1读取CSV文件,并格式化地保存进原始集合。由于文件是存储在硬盘上的,而硬盘的读写速度远比内存低。因此计算前先将数据读入到原始集合,然后按一定的格式(类或结构体)来存储每条数据是以后计算的基础。

    4.2单独抽出指数据标志(指数名称)保存进名称集合。后面对指数进行组合时,依据的是指数的唯一标志,即指数的名称。而带着几百条收盘价一起进行组合是没有必要且浪费资源的。输出所有读取到的指数(名称)给用户进行检查。

    4.3对数据标志进行两两组合。这里建立了一个递归函数,实现对输入N个元素中抽K个进行组合保存进配对集合。每种组合只出现一次,组合中无重复的元素,组合内元素无顺序区别。这样才能符合实际需求。该函数主要使用了高级的Linq查询技巧,但可能并不是时间和空间上的最优解,是一个可接受的解。

    4.4求配对集合中每个组合的相关度。建立一个与CORREL函数计算方法一样的代数计算函数。遍历组合,用每个组合中元素1和2的名称在原始集合中找到对应的收盘价数组,并一同放入计算函数中计算。这里就得到了与第三步中两两指数相关度一样的结果,并可以对结果进行检验,以验证组合函数和计算函数的正确性。

    4.5对数据标志进行K个元素的组合。按照用户的需求,从N个元素中每次抽取进行K个元素的组合,并存放进一个新的结果集合中。

    4.6遍历结果集合,组合出在每个组合(A组合)中元素的两两组合,然后从配对集合中查询出一样的组合并取出相关度放入A组合中。当查询完成时,A组合中就同时包含了K个元素和K个元素两两之间的所有相关度。这里的逻辑嵌套层次较多,文字描述不清楚可直接看代码。

    4.7对结果集合中的A组合进行排序,排序依据为每个A组合的平均相关度。按用户需求数量输出A组合的内容。

    4.8对用户输入的数据标志(指数名称)字符串进行分割。如输入了M个指数名称则利用循环进行M次查询。每次查询寻找出所有包含当前数据标志的A组合形成一个新的筛选集合,抛弃其他组合。然后下一个数据标志从刚刚得到的筛选集合中查询。经过M次比较后即可得出满足用户输入最终筛选集合。然后输出给用户检验。

    用法

    对我而然,首先将准备好的CSV文件和编译好的IndexCorrelation.exe放在同一目录下。然后打开EXE,输入CSV的文件名。

    如我想在原来已有的5个指数基础上再增加1个指数,则按6个指数一组进行组合。

    这时程序会给出一共可能产生的组合个数。之后我想阅览相关性最小的10个组合。

    程序会按平均相关性从小到大罗列。最后,我根据我现有的投资条件进行查询。

    根据我现在的投资条件,选择添加标普500进入我的指数定投序列会是平均相关性最低的选择,但不一定是最好的选择。这是显然的,因为标普500和A股指数、港股指数都是负相关的,只和纳斯达克是正相关的,会严重拉低整个平均相关性。

    后语

    老读者可能会发现,在准备原始集合的部分,完全可以参照《基于C#的股票数据获取并进行基础分析的程序》一文中的程序,从网上直接下载指数数据。是的,如果只是用于分析证券市场数据的相关性的确可以这样做。我目前使用文件导入的做法是为了方便后面可以对其他类型的数据进行分析。有心的读者可以自行拼接这两个程序。

    鉴于本文中的程序大量地使用了集合、泛型、Linq查询等技术。即使不用于进行相关性分析的读者也可以参考其中的编程技巧。再次声明,程序中的所有解法不一定是时间度量和空间度量上的最优解,只是我可以接受的解。

    集合(数组)在使用过程中,一定要注意越界访问的问题。我作为编程人员和使用者,会自然而然地只输入合法的条件进入程序。感兴趣的读者可以自行检查所有可能会发生越界访问的地方。

    IndexCorrelation

  • 使用机器学习预测世界杯赛果

    使用机器学习预测世界杯赛果

    目标

    本文的目标是在学习了TensorFlow的官方Graph Execution示例后,通过举一反三运用其他例子来进一步理解官方示例。因此,本文中的所有结果仅作学术研究用途。

    前提

    IDE准备

    Visual Studio 2017

    打开Visual Studio Installer修改当前使用的VS,确保工作负载Web和云分类下的Python开发被选中。然后切换到单个组件,确保开发活动中的Python语言支持编译器、生成工具和运行时中的Python 3 64-bit被勾选。

    点击右下角的修改,等待组件被下载和安装。

    项目准备

    新建一个Python应用程序项目。在解决方案资源管理器中找到Python环境,并右键添加虚拟环境。虚拟环境的基础解析器为Python 3中的某个版本。

    右键点击新建的虚拟环境,选择安装Python包

    分别安装pip、pandas和tensorflow。这些包在安装的同时,会把所有需要的包进行链式安装。以防万一,以下列出需要用到的所有包及版本。
    [code lang=”text” collapse=”true” title=”requirements.txt”]
    absl-py==0.2.2
    astor==0.6.2
    bleach==1.5.0
    certifi==2018.4.16
    chardet==3.0.4
    cycler==0.10.0
    gast==0.2.0
    get==1.0.3
    grpcio==1.13.0
    html5lib==0.9999999
    idna==2.7
    kiwisolver==1.0.1
    Markdown==2.6.11
    matplotlib==2.2.2
    numpy==1.14.5
    pandas==0.23.1
    pip==10.0.1
    post==1.0.2
    protobuf==3.6.0
    public==1.0.3
    pyparsing==2.2.0
    python-dateutil==2.7.3
    pytz==2018.5
    query-string==1.0.2
    request==1.0.2
    requests==2.19.1
    setuptools==39.2.0
    six==1.11.0
    tensorboard==1.8.0
    tensorflow==1.8.0
    termcolor==1.1.0
    urllib3==1.23
    Werkzeug==0.14.1
    wheel==0.31.1
    [/code]

    数据准备

    既然要进行机器学习,就需要准备好训练数据集测试数据集。训练数据集用于构建出一个能根据特征值推断出标签值模型,而测试数据集用于评估这个模型的准确度,它们都来自一个数据超集。为了有效地对模型进行评判,测试数据集与训练数据集不能有交集。

    我构建的数据超集每一行代表一场比赛,因为我希望以一场比赛为单位预测它的赛果,并有如下的列,并描述为什么我要纳入这些列:

    • 序号:描述这是第几场比赛。随着赛程时间的进行,球员逐渐适应气候环境的情况以及产生的疲倦都可能会影响赛果。
    • 主队:以数字的方式表示这场比赛的主队。我按出场顺序为每支球队定义了一个编号,实际上编号的大小无所谓,只要在整个数据集中用同一个编号表示同一支球队即可。编号对应的球队在后面会给出。
    • 客队:以数字的方式表示这场比赛的客队。
    • 主队胜的平均初始赔率:赔率代表着某家公司对该场比赛该队表现的看法,它本身即是各种可能影响比赛因素的一个综合提炼。而取多家公司的平均赔率更具客观性。
    • 平局的平均初始赔率
    • 主队负的平局初始赔率
    • 主队胜的平局即时赔率:通过与同一行中的初始赔率作比较,获得赔率变化。这个变化可能是因为某些临赛前影响比赛的因素变化所导致的,也可能是公司一开始在掩盖自己的真实意图。即时赔率在数据超集中为截止投注前的最后一次公布的赔率,而在预测条件中为当前得知的最后一次公布的赔率。
    • 平局的平均即时赔率
    • 主队负的平均即时赔率
    • 主队近10场比赛的胜场数:近期比赛会在一定程度上反映出该队伍的状态。
    • 主队近10场比赛的平场数
    • 主队近10场比赛的负场数
    • 客队近10场比赛的胜场数
    • 客队近10场比赛的平场数
    • 客队近10场比赛的负场数
    • 主队积分:这场比赛开始前,按小组赛规则的积分。因为数据集中也纳入了淘汰赛的数据,所以也按胜方积3分,平局各积1分,负方不积分的方式统计。积分在一定程度上反映了之前的比赛结果对该队伍造成的影响。
    • 客队积分
    • 赛果:以数字方式表述这场比赛的结果。主胜为2,平局为1,主负为0.

    前17列为特征列,每列中的每个值即为特征值。最后一列为标签列,每列中的每个值即为标签值。机器学习的目的就是找出已知的特征值与特征值组合和已知标签值之间的关联,然后根据已知的特征值去预测未知的标签。

    这些数据的来源通常可以在投注网站找得到。

    这时前提准备已经完成,可以往项目中添加.py文件及代码。

    代码展示

    先上代码,然后对代码进行讲解。
    [code lang="python" collapse="true" title="WorldCupPredict.py"]
    from __future__ import absolute_import
    from __future__ import division
    from __future__ import print_function

    import argparse
    import tensorflow as tf

    import game_data

    parser = argparse.ArgumentParser()
    parser.add_argument('–batch_size', default=4, type=int, help='捆绑大小')
    parser.add_argument('–train_steps', default=2000, type=int,
    help='训练步数')

    def main(argv):
    args = parser.parse_args(argv[1:])

    # 拉取数据
    (train_x, train_y), (test_x, test_y) = game_data.load_data()

    # 描述输入特征列
    my_feature_columns = []
    for key in train_x.keys():
    my_feature_columns.append(tf.feature_column.numeric_column(key=key))

    # 构建DNN分类器
    classifier = tf.estimator.DNNClassifier(
    feature_columns=my_feature_columns,
    # 3个隐藏层
    hidden_units=[20, 15, 5],
    # 设置3个类别
    n_classes=3)

    # 训练模型
    classifier.train(
    input_fn=lambda:game_data.train_input_fn(train_x, train_y,
    args.batch_size),
    steps=args.train_steps)

    # 评估模型
    eval_result = classifier.evaluate(
    input_fn=lambda:game_data.eval_input_fn(test_x, test_y,
    args.batch_size))

    print('\n测试集准确度: {accuracy:0.3f}\n'.format(**eval_result))

    # 建立需要使用模型做出预测的数据
    predict_x = {
    'Game': [57],
    'Home': [4],
    'Away': [9],
    'WinInitialOdds': [4.3],
    'DrawInitialOdds': [3.17],
    'LossInitialOdds': [2.00],
    'WinCurrentOdds': [4.44],
    'DrawCurrentOdds': [3.11],
    'LossCurrentOdds': [2.03],
    'HomeWinCount': [8],
    'HomeDrawCount': [1],
    'HomeLossCount': [1],
    'AwayWinCount': [6],
    'AwayDrawCount': [3],
    'AwayLossCount': [1],
    'HomePoint': [12],
    'AwayPoint': [10]
    }

    predictions = classifier.predict(
    input_fn=lambda:game_data.eval_input_fn(predict_x,
    labels=None,
    batch_size=args.batch_size))

    template = ('\预测结果是"{}" ({:.1f}%)"')

    for pred_dict in predictions:
    class_id = pred_dict['class_ids'][0]
    probability = pred_dict['probabilities'][class_id]

    print(template.format(game_data.RESULT[class_id],
    100 * probability))

    if __name__ == '__main__':
    tf.logging.set_verbosity(tf.logging.INFO)
    tf.app.run(main)
    [/code]

    [code lang="python" collapse="true" title="game_data.py"]
    import pandas as pd
    import tensorflow as tf

    TRAIN_URL = "http://120.77.11.178/wp-content/uploads/2018/07/game_training.csv"
    TEST_URL = "http://120.77.11.178/wp-content/uploads/2018/07/game_test.csv"

    CSV_COLUMN_NAMES = ['Game','Home','Away',
    'WinInitialOdds','DrawInitialOdds','LossInitialOdds',
    'WinCurrentOdds','DrawCurrentOdds','LossCurrentOdds',
    'HomeWinCount','HomeDrawCount','HomeLossCount',
    'AwayWinCount','AwayDrawCount','AwayLossCount',
    'HomePoint','AwayPoint','Result']

    RESULT = ['Loss','Draw','Win']

    def maybe_download():
    train_path = tf.keras.utils.get_file(TRAIN_URL.split('/')[-1], TRAIN_URL)
    test_path = tf.keras.utils.get_file(TEST_URL.split('/')[-1], TEST_URL)

    return train_path, test_path

    def load_data(y_name='Result'):
    """以(train_x, train_y), (test_x, test_y)的形式返回比赛数据集"""
    train_path, test_path = maybe_download()

    train = pd.read_csv(train_path, names=CSV_COLUMN_NAMES, header=0)
    train_x, train_y = train, train.pop(y_name)

    test = pd.read_csv(test_path, names=CSV_COLUMN_NAMES, header=0)
    test_x, test_y = test, test.pop(y_name)

    return (train_x, train_y), (test_x, test_y)

    def train_input_fn(features, labels, batch_size):
    """训练输入函数"""
    # 将输入转换为数据集
    dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels))

    # 打乱、重复以及捆绑样本
    dataset = dataset.shuffle(1000).repeat().batch(batch_size)

    # 返回数据集
    return dataset

    def eval_input_fn(features, labels, batch_size):
    """验证和预测输入函数"""
    features=dict(features)
    if labels is None:
    # 没有标签,只有特征
    inputs = features
    else:
    inputs = (features, labels)

    # 将输入转换为数据集
    dataset = tf.data.Dataset.from_tensor_slices(inputs)

    # 捆绑样本
    assert batch_size is not None, "batch_size must not be None"
    dataset = dataset.batch(batch_size)

    # 返回数据集
    return dataset

    CSV_TYPES = [[0.0], [0.0], [0.0],
    [0.0], [0.0], [0,0],
    [0.0], [0.0], [0.0],
    [0.0], [0.0], [0.0],
    [0.0], [0.0], [0.0],
    [0.0], [0.0], [0]]

    def _parse_line(line):
    # 解码行
    fields = tf.decode_csv(line, record_defaults=CSV_TYPES)

    # 将结果打包为字典
    features = dict(zip(CSV_COLUMN_NAMES, fields))

    # 将标签从特征中分离
    label = features.pop('Species')

    return features, label

    def csv_input_fn(csv_path, batch_size):
    # 创建一个包含多行文本的数据集
    dataset = tf.data.TextLineDataset(csv_path).skip(1)

    # 转换每一行
    dataset = dataset.map(_parse_line)

    # 打乱、重复和捆绑样本
    dataset = dataset.shuffle(1000).repeat().batch(batch_size)

    # 返回数据集
    return dataset
    [/code]
    首先说WorldCupPredict.py的代码。引用了一些固定的模块,然后确定了步数(step)和捆绑(batch)两个参数。步长并不是越大越好,模型的准确度会从第一步开始逐渐波动上升,到达某个最大值值后可能会下降一点然后再上升一点。当训练数据多的时候,捆绑值可以设大一点。具体意义参考TensorFlow官网的术语表。

    然后会调用game_data模块获得训练数据和测试数据。再对特征值进行标记。之后构建了一个分类器,因为实质上这是个按特征把比赛归类为主胜、平局和主负三种不同赛果的问题。

    分类器中设置了三个隐藏层,每一个特征值都会流进第一层的每个神经元里,第一层神经元的计算结果又会流进第二层,如此类推。直至输出到三个不同的赛果类别中。最佳的隐藏层数的设置和每层神经元数的设置需要通过重复实验和一定的经验决定。往往多的层和多的神经元需要更多的训练数据去达到有效的训练。

    训练完成后会使用测试数据对模型进行评估,并给出准确度。之后可以手动构造一个或多个比赛数据给模型预测。这里使用的是第57场乌拉圭对战法国的数据,这里并没有给出赛果作为标签值。

    模型会根据特征值预测出归属于不同标签的可能性,这里文本输出了最大可能性的标签及其可能性。

    接下来说一下game_data.py的代码。首先定义了两个数据源,采用的是本网站整理好的训练集和测试集。数据源的形式是csv文件,是一种以换行表示数据行,逗号表示一行中的数据列的表格型数据。

    首先需要从网络把数据源下载回到本地,然后将csv数据源整理成可以被计算的张量(tensor)。解析csv文件需要用到一个数据模板向程序说明每列是一个什么数据类型的数据。这里把前面17列的特征值作为浮点数,最后一列作为整数。因为张量转换过程中所有数据的类型要相同。这个文件的代码主要是做了数据整理的工作。

    运行结果

    过程中会给出在训练模型时的信息,包括了步数、进行到当前步数的损失和每秒进行了多少步。

    模型训练完成后,会放入测试集,用于评估模型的准确度。最后会给出一个无标签的特征值的标签预测结果。如上图所示,即评估准确度达到50%,而有39.5%的概率本场(乌拉圭VS法国)的比赛中,主队(乌拉圭)会输。

    读者可以尝试进行以下几种工作:

    1. 调整步长和捆绑对训练和评估的影响。
    2. 调整隐藏层数和神经元个数对训练和评估的影响。
    3. 加入新的特征列和删除已有特征列对训练和评估的影响。
    4. 把该项目作为模板处理其他分类问题。
    5. 壕砸2块钱法国赢。

    数据集文件

    点此下载

  • 基于C#的股票数据获取并进行基础分析的程序

    基于C#的股票数据获取并进行基础分析的程序

    本程序基于C#控制台应用开发,二进制文件(文末附下载地址)需求支持库.NET Framework 4.6.1。主要实现了从HTTPS的JSON数据源获取结构化的股票数据,然后对数据进行分析处理,给出分析结论的功能。

    先放上代码,后面再对代码进行解释。
    [code lang=”csharp” collapse=”true” title=”program.cs”]
    using System;
    using System.Collections.Generic;

    namespace FundsInvestment
    {
    class Program
    {

    static void Main(string[] args)
    {
    Console.WriteLine("!!!免责声明!!!");
    Console.WriteLine("本程序仅用于演示如何从JSON数据源获取数据,并进行基本的分析");
    Console.WriteLine("演示数据源:雪球 www.xueqiu.com");
    Console.WriteLine("演示分析算法:蚂蚁财富 慧定投");
    Console.WriteLine("自动放弃本程序版权,不收取费用或打赏,也不会对你的投资盈亏负责。");
    Console.WriteLine("PROGRAMMING BY:暂置元帅");
    Console.WriteLine("——————–");
    List<Index> list = new List<Index>(5)
    {
    new Index("SH000016"),
    new Index("SH000905"),
    new Index("SZ399006"),
    new Index("NASDAQ"),
    new Index("HKHSCEI")
    };
    foreach (var item in list)
    {
    if (item.FetchData() == true)
    {
    Console.WriteLine(item.Symbol+"拉取数据成功");
    Console.WriteLine("最近一日收盘价" + item.Close);
    Console.WriteLine(item.GetAntFortuneAlgrithmAdvice());
    }
    else
    {
    Console.WriteLine("拉取数据失败");
    }
    Console.WriteLine("——————–");
    }
    while (true)
    {
    try
    {
    Console.Write("输入股票或指数代码(SH/SZ+六位数字):");
    string symbol = Console.ReadLine();
    Console.Write("输入最后一个查询日(年/月/日):");
    DateTime endTime = DateTime.Parse(Console.ReadLine());
    Console.Write("输入往前查询天数(整数):");
    int dayCount = Int32.Parse(Console.ReadLine());
    Console.Write("基础投资额:");
    double baseLine = double.Parse(Console.ReadLine());
    Console.WriteLine("——–数据拉取中——–");

    Index myIndex = new Index(symbol, endTime, dayCount);
    myIndex.BaseLine = baseLine;
    if (myIndex.FetchData() == true)
    {
    Console.WriteLine(myIndex.Symbol + "拉取数据成功");
    Console.WriteLine("最近一日收盘价" + myIndex.Close);
    Console.WriteLine(myIndex.GetAntFortuneAlgrithmAdvice());
    }
    else
    {
    Console.WriteLine("拉取数据失败");
    }
    Console.WriteLine("——————–");
    }
    catch (Exception)
    {
    Console.WriteLine("输入格式有误");
    }

    }
    }
    }
    }
    [/code]
    [code lang=”csharp” collapse=”true” title=”Index.cs”]
    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;

    namespace FundsInvestment
    {
    /// <summary>
    /// 指数类
    /// </summary>
    class Index
    {
    /// <summary>
    /// 指数符号
    /// </summary>
    public string Symbol { get; set; }

    /// <summary>
    /// 数据储存对象
    /// </summary>
    private Rootobject DataArray { get; set; }

    /// <summary>
    /// 数据结束日期时间戳
    /// </summary>
    private string EndTimeStamp { get; set; }

    /// <summary>
    /// 数据起始日期时间戳
    /// </summary>
    private string BeginTimeStamp { get; set; }

    /// <summary>
    /// 交易日数量
    /// </summary>
    private int ExchangeDayCount { get; set; }

    /// <summary>
    /// 均值
    /// </summary>
    public double MA { get { return GetMA(); } }

    /// <summary>
    /// 投资基线(默认值20元)
    /// </summary>
    private double baseLine = 20;

    /// <summary>
    /// 投资基线(默认值20元)
    /// </summary>
    public double BaseLine { get => baseLine; set => baseLine = value; }

    /// <summary>
    /// 最近一日收盘价
    /// </summary>
    public double Close
    {
    get
    {
    if(DataArray != null && DataArray.chartlist.Length != 0)
    {
    return DataArray.chartlist.Last().close;
    }
    return 0;
    }
    }

    /// <summary>
    /// 构造指数查询地址(默认从180个交易日前到今天)
    /// </summary>
    /// <param name="Symbol">指数符号</param>
    public Index(string Symbol)
    {
    this.Symbol = Symbol;
    DateTime endDay = DateTime.Today;
    //超量请求数据,填充非交易日
    DateTime beginDay = new DateTime(endDay.Ticks – new TimeSpan(180 * 2, 0, 0, 0).Ticks);
    BeginTimeStamp = GetTimeStamp(beginDay);
    EndTimeStamp = GetTimeStamp(endDay);
    ExchangeDayCount = 180;
    }

    /// <summary>
    /// 构造指数查询地址
    /// </summary>
    /// <param name="Symbol">指数符号</param>
    /// <param name="EndDay">结束日期</param>
    /// <param name="DayCount">交易日个数</param>
    public Index(string Symbol,DateTime EndDay,int DayCount)
    {
    this.Symbol = Symbol;
    DateTime endDay = EndDay;
    //超量请求数据,填充非交易日
    DateTime beginDay = new DateTime(endDay.Ticks – new TimeSpan(DayCount * 2, 0, 0, 0).Ticks);
    BeginTimeStamp = GetTimeStamp(beginDay);
    EndTimeStamp = GetTimeStamp(endDay);
    ExchangeDayCount = DayCount;
    }

    /// <summary>
    /// 拉取数据
    /// </summary>
    /// <returns>是否拉取成功</returns>
    public bool FetchData()
    {
    string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36";
    CookieContainer container = new CookieContainer();
    try
    {
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://www.xueqiu.com");
    request.UserAgent = UserAgent;
    request.Method = "GET";
    request.CookieContainer = container;
    HttpWebResponse response = (HttpWebResponse)request.GetResponse();

    var jsonRequest = WebRequest.CreateHttp(BuildUri());
    jsonRequest.UserAgent = UserAgent;
    jsonRequest.CookieContainer = container;
    jsonRequest.Method = "GET";
    HttpWebResponse jsonResponse = (HttpWebResponse)jsonRequest.GetResponse();
    var stream = jsonResponse.GetResponseStream();
    var streamReader = new StreamReader(stream);
    var content = streamReader.ReadToEnd();

    DataArray = JsonConvert.DeserializeObject<Rootobject>(content);

    if(DataArray.success == "true")
    {
    return true;
    }
    else
    {
    return false;
    }
    }
    catch (Exception e)
    {
    return false;
    }
    }

    /// <summary>
    /// 获得蚂蚁财富慧定投算法推荐定投额
    /// </summary>
    public string GetAntFortuneAlgrithmAdvice()
    {
    int length = DataArray.chartlist.Length;
    if (length == 0)
    {
    return "无可供计算的数据";
    }
    string advice = string.Empty;
    double ma = GetMA();
    double indexValue = DataArray.chartlist.Last().close;
    if(indexValue > ma)
    {
    double rate = (indexValue – ma) / ma;
    advice += "当前指数值高于均线" + Math.Round((rate * 100),2) + "%,建议本轮投资";
    if (rate < 0.15)
    {
    advice += (BaseLine * 0.9) + "元";
    }
    else if(rate >= 0.15 && rate < 0.5)
    {
    advice += (BaseLine * 0.8) + "元";
    }
    else if (rate >= 0.5 && rate < 1)
    {
    advice += (BaseLine * 0.7) + "元";
    }
    else if (rate >= 1)
    {
    advice += (BaseLine * 0.6) + "元";
    }
    }
    else if(indexValue == ma)
    {
    advice += ("当前指数值等于均线,建议本轮投资" + BaseLine + "元");
    }
    else if(indexValue < ma)
    {
    double rate = (ma – indexValue) / ma;
    advice += "当前指数值低于均线" + Math.Round((rate * 100),2) + "%,";
    List<double> tenDay = new List<double>(10);
    int start = 0;
    //防止数组总长小于10
    if(length >= 10)
    {
    start = length – 10;
    }
    else
    {
    start = 0;
    }
    for (; start < length; start++)
    {
    tenDay.Add(DataArray.chartlist[start].close);
    }
    if ((tenDay.Max() / tenDay.Min()) – 1 > 0.05)
    {
    advice += "过去10日振幅大于5%,建议本轮投资";
    if(rate < 0.05)
    {
    advice += (BaseLine * 0.6) + "元";
    }
    else if(rate >= 0.05 && rate < 0.1)
    {
    advice += (BaseLine * 0.7) + "元";
    }
    else if (rate >= 0.1 && rate < 0.2)
    {
    advice += (BaseLine * 0.8) + "元";
    }
    else if (rate >= 0.2 && rate < 0.3)
    {
    advice += (BaseLine * 0.9) + "元";
    }
    else if (rate >= 0.3 && rate < 0.4)
    {
    advice += BaseLine + "元";
    }
    else if (rate >= 0.4)
    {
    advice += (BaseLine * 1.1) + "元";
    }
    }
    else if ((tenDay.Max() / tenDay.Min()) – 1 <= 0.05)
    {
    advice += "过去10日振幅小于或等于5%,建议本轮投资";
    if (rate < 0.05)
    {
    advice += (BaseLine * 1.6) + "元";
    }
    else if (rate >= 0.05 && rate < 0.1)
    {
    advice += (BaseLine * 1.7) + "元";
    }
    else if (rate >= 0.1 && rate < 0.2)
    {
    advice += (BaseLine * 1.8) + "元";
    }
    else if (rate >= 0.2 && rate < 0.3)
    {
    advice += (BaseLine * 1.9) + "元";
    }
    else if (rate >= 0.3 && rate < 0.4)
    {
    advice += (BaseLine * 2) + "元";
    }
    else if (rate >= 0.4)
    {
    advice += (BaseLine * 2.1) + "元";
    }
    }
    }
    return advice;
    }

    /// <summary>
    /// 获取均值
    /// </summary>
    /// <returns>均值</returns>
    private double GetMA()
    {
    double summary = 0;
    int length = DataArray.chartlist.Length;
    for (int i = length – ExchangeDayCount; i < length; i++)
    {
    summary += DataArray.chartlist[i].close;
    }
    return summary / ExchangeDayCount;
    }

    /// <summary>
    /// 构造指数JSON数据源请求地址
    /// </summary>
    /// <returns>请求地址</returns>
    private string BuildUri()
    {
    string requestUri = string.Empty;
    string headStr = "https://xueqiu.com/stock/forchartk/stocklist.json?";
    requestUri += headStr;
    string symbolStr = "symbol=" + Symbol;
    requestUri += symbolStr;
    requestUri += "&";
    string periodStr = "period=1day";
    requestUri += periodStr;
    requestUri += "&";
    string typeStr = "type=before";
    requestUri += typeStr;
    requestUri += "&";
    string beginStr = "begin=" + BeginTimeStamp;
    requestUri += beginStr;
    requestUri += "&";
    string endStr = "end=" + EndTimeStamp;
    requestUri += endStr;
    return requestUri;
    }

    /// <summary>
    /// 获得时间戳
    /// </summary>
    /// <param name="time">时间</param>
    /// <returns>时间戳</returns>
    private string GetTimeStamp(DateTime time)
    {
    TimeSpan timeSpan = time – new DateTime(1970, 1, 1, 0, 0, 0);
    return Convert.ToInt64(timeSpan.Ticks / 10000).ToString();
    }
    }
    }
    [/code]
    [code lang=”csharp” collapse=”true” title=”Rootobject.cs”]
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    namespace FundsInvestment
    {
    public class Rootobject
    {
    /// <summary>
    /// 股票对象
    /// </summary>
    public Stock stock { get; set; }
    /// <summary>
    /// 查询是否成功
    /// </summary>
    public string success { get; set; }
    public Chartlist[] chartlist { get; set; }
    public class Stock
    {
    /// <summary>
    /// 股票符号
    /// </summary>
    public string symbol { get; set; }
    }
    public class Chartlist
    {
    public long volume { get; set; }
    /// <summary>
    /// 开盘价
    /// </summary>
    public double open { get; set; }
    /// <summary>
    /// 最高价
    /// </summary>
    public double high { get; set; }
    /// <summary>
    /// 收盘价
    /// </summary>
    public double close { get; set; }
    /// <summary>
    /// 最低价
    /// </summary>
    public double low { get; set; }
    /// <summary>
    /// 涨跌额
    /// </summary>
    public double chg { get; set; }
    /// <summary>
    /// 涨跌幅
    /// </summary>
    public double percent { get; set; }
    /// <summary>
    /// 换手率
    /// </summary>
    public double turnrate { get; set; }
    /// <summary>
    /// 5日均值
    /// </summary>
    public double ma5 { get; set; }
    /// <summary>
    /// 10日均值
    /// </summary>
    public double ma10 { get; set; }
    /// <summary>
    /// 20日均值
    /// </summary>
    public double ma20 { get; set; }
    /// <summary>
    /// 30日均值
    /// </summary>
    public double ma30 { get; set; }
    /// <summary>
    /// DIFF值
    /// </summary>
    public double dif { get; set; }
    /// <summary>
    /// DEA值
    /// </summary>
    public double dea { get; set; }
    /// <summary>
    /// MACD值
    /// </summary>
    public double macd { get; set; }
    public long lot_volume { get; set; }
    public long timestamp { get; set; }
    public string time { get; set; }
    }
    }
    }
    [/code]
    Program.cs类是项目自动生成的类。作为程序的入口,并对程序的执行流程进行控制。流程控制主要分为几个部分:1.声明免责信息。2.构造Index类的对象集合,处理例子数据。3.通过无限循环读取用户输入的值构造Index对象,处理特定数据。

    其中使用了Index类的两种构造方法。第一种只需输入股票符号,默认按从180个交易日前到今天进行数据获取。第二种需要输入股票符号、结束日期和交易日个数。

    Rootobject.cs类由JSON字符串通过Visual Studio 2017的“将JSON粘贴为类”功能自动生成,作为反序列化后的模板和DataModel使用。需要注意的是,某些字段的基本变量类型要定义为长整形才由足够的空间存放数据。

    Index.cs类为本程序的业务逻辑类。由构造函数负责获取拼接出股票数据源地址所需的所有参数。

    FetchData方法中使用了HttpWebRequest去获取雪球数据源地址的JSON数据,并把JSON字符串反序列化为Rootobject对象,最后判断是否正确获得数据。这里两次进行GET Request是因为数据源需要先访问主页获取到Cookies权限才能正常访问到数据。

    GetAntFortuneAlgrithmAdvice方法对指定交易日内的数据进行了遍历统计和分析。分析方法参照了蚂蚁财富的慧定投算法。

    编写这个程序的目的是为了按照慧定投的模式对我购买5只互相关度最低指数型基金进行投资,关于股市指数的相关度自动计算程序可能以后会开发。

    慧定投官方给出的跟踪标的并没有我需要的指数,而每个投资周期都去手动计算投资额又过于繁琐。所以本着“重复性的劳动交给程序处理,……”这一人生哲学信条,开发了本程序以解放劳动,同时得以回馈社区。

    最后再次声明:本程序不代表任何投资建议,入市自负盈亏。

    二进制文件

  • dot Net体系命名指引——第二节:选词规则

    dot Net体系命名指引——第二节:选词规则

    .net 体系命名指引

    第二节 选词规则

    原则一:选择易读性强的命名方式。

    解释:当名称由多个单词组成时,以可读性最强的方式排列它们。

    例子:当需要命名一个表格形式的视图容器成员变量时,使用GridView可以比ViewGrid更清晰的定义这个变量。因为“View”既可以作为名词,也可以作为动词。当作为后者时,容易与函数命名的“动词+名词”模式搞混。

    原则二:易读性优于缩略性。

    解释:诚然使用较短的名称能够缩短每行代码的长度,但它不应该以牺牲易读性为前提。

    例子:理科公式中很多量使用了单个西文字母表示,当需要将这些公式编程为函数时,这些量应该用其本来代表的实际意义来表示,至少也要用其读音来表示希腊字母。例如“ω”在某些公式中代表角度,这个量应该命名为Angle,至少也要命名为Omiga,而不是一个“w”。

    原则三:尽量只用“英文字母和数字”的字符。

    解释:即使某些语言允许使用除了英文字母和数字之外的字符来命名,也要少用。一定要用也要有统一的规则。

    例子:为界面上的控件——“确定”按钮命名时,使用BtnConfirm已经能够清晰地分割“按钮”和“确定”两个语义。但是,有时Btn_Confirm这样的命名方式更快地传达出这是一个UI控件的成员,通常由模板生成的,不能自由删改。

    原则四:彻底抛弃匈牙利命名法。

    解释:使用帕斯卡命名法和驼峰命名法已经足够对付dot Net体系的程序元素。首先目前宇宙最强大的IDE——Visual Studio 2017已经能够非常娴熟地对付类型不匹配等早期错误。然后匈牙利命名法和帕斯卡命名法是完全冲突的。

    例子:虽然不应该严格遵守匈牙利命名法,但是其中某些要素是可以吸收改造的。可以使用Is、Can等含有是否、能否语义的单词来代替b组合一个布尔型变量,如使用IsOpen而不是bOpen

    原则五:不使用被各种编程语言广泛作为关键字的单词。

    解释:很明显这会和编程语言本身产生语义级别的冲突。需要注意的是,C#的关键字就有一百多个。

    例子:readonly是关键字之一,所以表示一个只读的变量时,使用IsReadonly

    关于缩略语

    不要自定缩略语。尤其是自定的缩略语会和已有的单词产生歧义。

    尽量少使用缩略语,即使要使用缩略语,也要用被广泛接受的缩略语。

    关于语言特有的命名

    关注语义去命名而不是语言特性关键字。

    例如GetLengthGetSizeGetCount是不同类里面的方法,返回的是一个整型的变量。这时使用前三者而不是GetInt更好。

    使用一些常见的名字,如valueitem,而不是将成员和类型的名字起得一样。除非这个成员没有明显的语义,或者作为函数参数来说它的类型不重要。

  • 本站支持程序迁移至WordPress

    本站支持程序迁移至WordPress

    很遗憾,本站的原支持程序BlogEngine.Net已经许久没有发布新版本了。

    这意味着它已经进入了衰退期。

    所以本站的支持程序选用了世界上最流行的博客程序——WordPress。

    除了文章迁移了之外,其他内容已经永久删除。

    特此通告,万一有人订阅了RSS呢。