发布于 2026-01-06 0 阅读
0

使用 .NET 分析股票

使用 .NET 分析股票

年复一年,对冲基金经理们总能设法跑赢基准指数,甚至在市场逆风的情况下也能盈利。他们是如何做到的?这的确是一个值得探讨的问题。对此,答案不一而足。其中很大一部分原因在于地理位置和基础设施。而我比较欣慰的是,他们主要依靠的是许多程序员都熟悉的计算模型和算法。他们构建的模型能够从多个层面分析股票,并编写程序来执行这些模型训练出的交易策略。大多数对冲基金的量化分析师(Quant)都拥有计算机科学、数学、工程和物理等专业背景,而非传统的MBA学位,或者说除了MBA之外,他们还拥有这些背景。而正是他们在这些领域习得的技能,为他们带来了巨大的价值。

在这篇文章中,我们将简要介绍这些方法。我们将学习如何研究股票的历史价格和成交量数据,以尝试预测其未来的表现。

技术分析

技术分析是对股票历史价格和成交量数据的研究,旨在预测其未来走势。在开始之前,我要先强调一点。根据有效市场假说的某些版本,所有信息都已反映在股票价格中。因此,研究历史数据并不能带来任何优势。尽管如此,对冲基金仍然坚持分析这些趋势,我们也将效仿!

什么是指标

我们将尝试从历史价格数据中提取指标,但什么是指标呢?指标本质上是一个能够“指示”价格走势的数字。通常,我们会寻找价格异常值,以确定股票是否处于“超卖”(价格低于其应有水平)或“超买”(价格高于其应有水平)状态。如果我们检测到这些信号,就可以对股票进行操作,并在股价回调期间,利用略微不有效的市场进行套利。我们将关注两个这样的指标——简单移动平均线 (SMA) 比率和布林带百分比 (BBP)。此外,我们还将关注用于计算 SMA 比率和 BBP 的其他几个指标,包括 SMA、滚动波动率和布林带。

创建项目

现在我们直接进入代码部分,请创建一个 .NET Core 控制台应用程序项目,并将以下 NuGet 包添加到该项目中:

Daany.DataFrame
Numpy
ScottPlot
Enter fullscreen mode Exit fullscreen mode

我们接下来要做的几乎所有工作都深受 Python 的影响。我们将使用 NumPy 库来加速分析,而 NumPy 又依赖于它自带的 Python 运行时环境。

现在创建一个名为 的文件夹data,以及文件BollingerBandsPercentage.csRollingVolatility.csSimpleMovingAverage.cs

获取历史数据

技术分析的关键在于历史股票数据,这些数据在雅虎财经上很容易找到。只需搜索特定股票,进入其Historical Data标签页,选择要获取记录的时间段,然后下载即可。这些记录以逗号分隔值 (CSV) 文件格式提供。我将获取苹果 (AAPL)、摩根大通 (JPM) 和一只主要的标普 500 指数 ETF (SPY) 五年的数据。将这三个 CSV 文件添加到您的文件夹中。data在 Visual Studio 中,将生成操作设置为“CSV”,Content并将“复制到输出目录”设置为“ /usr/local/bin” Copy Always

数据解读

现在我们有了所有这些历史股票数据,接下来我们要读取它们。我们将使用相应的DataFrame.FromCsv方法,并将数据存储在一个DataFrame对象中。我们还会声明一个常量 int WINDOW,它将作为计算窗口均值的窗口范围。

const int WINDOW = 20;
var symbol = "AAPL";
var df = DataFrame.FromCsv(Path.Join("data", $"{symbol}.csv"));
Enter fullscreen mode Exit fullscreen mode

df 变量现在将包含一个数据框,它是该文件在内存中的表示形式.csv。正如我们之前讨论的,技术分析中我们可以研究两个要素:成交量和价格。这些 CSV 文件包含多个价格,以及Open记录每个日期对应的成交量和成交量HighLowCloseAdj CloseVolumeDate

开盘价是指股票在开盘时的股价。最高价是指股票当日交易的最高价。相反,最低价是指股票当日交易的最低价。

然后还有两个字段代表当日的收盘价。“收盘价”是指股票在当日收盘时的交易价格。“调整后收盘价”是指根据股票拆分或股息调整后的收盘价。每当发生股票拆分或股息时,“调整后收盘价”都需要更新。在接下来的示例中,Adj Close我们将重点关注这个字段。

提取近义词

它们在 C# 中更像是字典,而不是传统的 Python Pandas 库。你可以通过索引DataFrame访问调整后收盘价的集合。然后我们可以使用一些 LINQ 代码将这些值提取到一个数组中。DataFrameAdj Close

我将把这些数据存储在 NumPy 数组中,并截取前 20 条记录,因为这是我们的窗口大小。

var close = np.array(df["Adj Close"].Select(f => Convert.ToDouble(f)).ToArray()[rng])[$"{WINDOW - 1}:"];
Enter fullscreen mode Exit fullscreen mode

注意我们是如何访问 NumPy 数组内部的范围的吗?它看起来很像传统的 NumPy 切片表示法,只不过这里使用的是字符串。

计算简单移动平均线

我们要看的指标中最简单的(请原谅我用了这个双关语)是简单移动平均线(SMA)。对于数据框中的每个日期,SMA 是当前点及其前一个交易日窗口的平均值。我们将使用 20 个交易日的窗口。因此,如果我们想计算 2015 年 9 月 29 日的 SMA,我们将取当天及其前 19 个交易日的 SMA 值,将它们相加,然后除以 20。DataFrame 结构的一个优点是它使得计算这些滚动乘积变得容易。我定义了一个单独的类来运行它SimpleMovingAverage,但该方法本身只是一个静态方法NDarray

public class SimpleMovingAverage
{
    const int WINDOW = 20;
    public static NDarray CalculateSma(DataFrame df, Range rng)
    {
        return np.array(df.Rolling(WINDOW, Aggregation.Avg)["Adj Close"].Select(f => Convert.ToDouble(f)).ToArray()[rng])[$"{WINDOW - 1}:"];
    }
}
Enter fullscreen mode Exit fullscreen mode

很简单,对吧?我们来看看这里在做什么。我们调用Rolling了数据框,传入一个WINDOW包含 20 的参数,并告诉它计算平均值。然后,我们访问Adj Close数据框中的列,并提取出该行。我们删除了前 20 行,因为前 20 个结果无效,NaN无法进行有效的计算。

计算滚动波动率

我们需要关注的另一个指标是波动率。通常来说,我们认为股票投资的理想状态是高回报(盈利金额)和低风险——低风险高回报!我们可以通过计算特定股票相对于其滚动均值的标准差来衡量其波动率。这个计算方法类似于计算简单移动平均线(SMA),只是对于每个数据点,我们计算的是其滚动标准差。

public class RollingVolatility
{
    const int WINDOW = 20;
    public static NDarray CalculateRollingVolatility(DataFrame df, Range rng)
    {
        return np.array(df.Rolling(WINDOW, Aggregation.Std)["Adj Close"].Select(f => Convert.ToDouble(f)).ToArray()[rng])[$"{WINDOW - 1}:"];
    }
}
Enter fullscreen mode Exit fullscreen mode

布林带®

BB 指标易于理解,是衡量股票价格相对于我们预期价格水平的有力指标。

苹果布林带

在上图中,我们有三条线:上轨、下轨和收盘线。上轨是布林带的上限;下轨是下限;收盘线是股票当日的最终成交价。在研究股票时,我们通常会使用指标来观察阈值事件。在这种情况下,我们寻找的指标是股票价格是否突破上轨或下轨。如果股价突破上轨,通常表明股票处于超买状态,即将回落。当股票处于超卖状态时,可能是做空的好时机即押注股价下跌)。相反,如果股价跌破布林带下轨,则被视为超卖,即将上涨。

计算布林带非常简单,只要你已经有了简单移动平均线(SMA)和移动标准差。只需将标准差乘以 2,然后将结果加到 SMA 上,即可得到上轨。再减去这个结果,即可得到下轨。有了我们已经构建好的模型,这简直轻而易举。

var sma = SimpleMovingAverage.CalculateSma(df, rng);
var std2x = 2 * RollingVolatility.CalculateRollingVolatility(df, rng);
var upper = sma + std2x;
var lower = sma - std2x;
Enter fullscreen mode Exit fullscreen mode

这就是布林带的原理;布林带其实就是简单移动平均线上下两个标准差的范围。

布林带并非绝对可靠,当然也可能出错。例如,特斯拉(TSLA)目前的股价与布林带走势严重不符。如果你按照这种策略操作,损失几乎肯定会导致你的账户被追缴保证金——就我个人而言,考虑到这种风险和股价波动,我绝对不会涉足!

特斯拉的布林带

布林带百分比

现在我们有了布林带,接下来我们需要一个易于计算的数值来作为股票交易时机的指标。布林带百分比正好满足这个需求。它的计算方法是:先计算价格与下轨的差值,再除以上轨与下轨的差值。如果结果大于 1,则表示价格已突破上轨;如果结果小于 0,则表示价格已跌破下轨。目前,我们只需一行代码即可完成计算。

var bbp = (close - lower) / (upper - lower);
Enter fullscreen mode Exit fullscreen mode

简单移动平均比率

另一个可以用来判断股票是否超买或超卖的指标是简单移动平均线(SMA)比率。你可以通过将价格除以SMA来计算该比率。该指标也可以告诉你股票是超买还是超卖。不过,其阈值比较模糊,我听说0.95到1.05之间是正常的,高于0.95表示超买,低于0.05表示超卖——但这可能很大程度上取决于股票的具体情况。无论如何,只要知道SMA和收盘价,计算起来也很简单:

var smaRatio = close / sma;
Enter fullscreen mode Exit fullscreen mode

显示指标

现在我们已经计算好了,接下来要展示它们。我们将使用ScottPlotDataGen.Consecutive ,它提供了非常强大的绘图功能。我们将以散点图的形式展示这些数据。由于数据点非常密集,这些散点图看起来像线条。首先,初始化一个绘图对象并设置其大小,然后通过传入数据点的数量来生成一组连续的数据点。

var plt = new Plot(600, 400);
double[] xs = DataGen.Consecutive(bbp.size);
Enter fullscreen mode Exit fullscreen mode

绘制SMA比率

我们首先绘制SMA比率图。给图表添加标题,然后绘制数据xssmaRatio(调用NDArray的GetData函数获取其中的所有数据),添加标签并将标记大小设置为1。

接下来,将图例放在左下角。标记 Y 轴Ratio和 X 轴Day,然后使用该函数将图表保存为 png 格式SaveFig,并清除图表内容。

plt.Title($"SMA Ratio for {symbol} {startDate.ToShortDateString()} to {endDate.ToShortDateString()}");
plt.PlotScatter(xs, smaRatio.GetData<double>(), label: "SmaRatio", markerSize: 1);
plt.Legend(location: legendLocation.lowerLeft);
plt.YLabel("Ratio");
plt.XLabel("Day");
plt.SaveFig("SmaRatio.png");
plt.Clear();
Enter fullscreen mode Exit fullscreen mode

其他项目的处理过程非常相似。对于布林带,你需要绘制三组不同的数据,我发现把图例放在左上角更容易操作。

绘制布林带

plt.Title($"Bollinger Bands® for {symbol} {startDate.ToShortDateString()} to {endDate.ToShortDateString()}");
plt.PlotScatter(xs, close.GetData<double>(), label: "Close", markerSize: 1);
plt.PlotScatter(xs, upper.GetData<double>(), label: "Upper", markerSize: 1);
plt.PlotScatter(xs, lower.GetData<double>(), label: "Lower", markerSize: 1);
plt.YLabel("Dollars");
plt.XLabel("Day");
plt.Legend(location: legendLocation.upperLeft);
plt.SaveFig("BB.png");
plt.Clear();
Enter fullscreen mode Exit fullscreen mode

绘制布林带百分比图

plt.Title($"Bollinger Bands® Percentage for {symbol} {startDate.ToShortDateString()} to {endDate.ToShortDateString()}");
plt.PlotScatter(xs, bbp.GetData<double>(), label: "BBP", markerSize: 1);
plt.YLabel("Dollars");
plt.XLabel("Day");
plt.Legend(location: legendLocation.lowerLeft);
plt.SaveFig("BBP.png");
plt.Clear();
Enter fullscreen mode Exit fullscreen mode

运行这些命令后,图表将写入我们的 bin 目录,内容大致如下:

BB.png

布林带

BBP.png

布林带百分比

SmaRatio.png

SMA比率

总结

还有许多其他指标可供参考,每个指标都有其优势。但这些只是几个比较简单的指标,可以用来激发你的兴趣。这是一个引人入胜的领域,我们目前只是略窥门径。当然,投资前务必咨询财务顾问——但希望本文能让您对一些交易策略的运作方式有所了解!

后续步骤

既然我们已经创建了一些可供参考的指标,就可以开始考虑如何将它们运用到交易策略中了。上面我列出了一些关于如何在策略开发中使用这些指标的经验法则。现在我们已经将这些指标整理得井井有条,可以利用它们来创建机器学习模型,让模型来帮我们做出交易决策。

资源

文章来源:https://dev.to/slorello/analyzing-stocks-with-net-djd