基本面选股是通过分析公司的财务数据和市场表现来选择股票的一种方法。通过对公司的财务数据和市场表现进行深入分析,投资者可以更好地识别出具有投资价值的股票。
9.1 基本面数据

基本面数据名称为company.btx,提供 2007 年至今的财务数据,每季度一条。
数据示例:

数据结构:
|------------------------------|------------------------|--------------------------------------------------------|
| 字段名 | 含义 | 备注 |
| code | 股票代码 ||
| statdate | 财报统计的季度的最后一天 ||
| pubDate | 公司发布财报的日期 ||
| roeAvg | 净资产收益率(平均)(%) | 归属母公司股东净利润/[(期初归属母公司股东的权益+期末归属母公司股东的权益)/2]*100% |
| npMargin | (%)销售净利率 | 净利润/营业收入*100% |
| gpMargin | (%)销售毛利率 | 毛利/营业收入*100%=(营业收入-营业成本)/营业收入*100% |
| netProfit | 净利润(元) ||
| epsTTM | 每股收益 | 归属母公司股东的净利润TTM/最新总股本 |
| MBRevenue | 主营营业收入(元) ||
| totalShare | 总股本 ||
| liqaShare | 流通股本 ||
| YOYEquity | 净资产同比增长率 | (本期净资产-上年同期净资产)/上年同期净资产的绝对值*100% |
| YOYAsset | 总资产同比增长率 | (本期总资产-上年同期总资产)/上年同期总资产的绝对值*100% |
| YOYNI | 净利润同比增长率 | (本期净利润-上年同期净利润)/上年同期净利润的绝对值*100% |
| YOYEPSBasic | 基本每股收益同比增长率 | (本期基本每股收益-上年同期基本每股收益)/上年同期基本每股收益的绝对值*100% |
| YOYPNI | 归属母公司股东净利润同比增长率 | (本期归属母公司股东净利润-上年同期归属母公司股东净利润)/上年同期归属母公司股东净利润的绝对值*100% |
| currentRatio | 流动比率 | 流动资产/流动负债 |
| quickRatio | 速动比率 | (流动资产-存货净额)/流动负债 |
| cashRatio | 现金比率 | (货币资金+交易性金融资产)/流动负债 |
| YOYLiability | 总负债同比增长率 | (本期总负债-上年同期总负债)/上年同期中负债的绝对值*100% |
| liabilityToAsset | 资产负债率 | 负债总额/资产总额 |
| assetToEquity | 权益乘数 | 资产总额/股东权益总额=1/(1-资产负债率) |
| CAToAsset | 流动资产除以总资产 ||
| NCAToAsset | 非流动资产除以总资产 ||
| tangibleAssetToAsset | 有形资产除以总资产 ||
| ebitToInterest | 已获利息倍数 | 息税前利润/利息费用 |
| CFOToOR | 经营活动产生的现金流量净额除以营业收入 ||
| CFOToNP | 经营性现金净流量除以净利润 ||
| CFOToGr | 经营性现金净流量除以营业总收入 ||
| dupontROE | 净资产收益率 | 归属母公司股东净利润/[(期初归属母公司股东的权益+期末归属母公司股东的权益)/2]*100% |
| dupontAssetStoEquity | 权益乘数,反映企业财务杠杆效应强弱和财务风险 | 平均总资产/平均归属于母公司的股东权益 |
| dupontAssetTurn | 总资产周转率,反映企业资产管理效率的指标 | 营业总收入/[(期初资产总额+期末资产总额)/2] |
| dupontPnitoni | 归属母公司股东的净利润/净利润,反映母公司控股子公司百分比。如果企业追加投资,扩大持股比例,则本指标会增加。 ||
| dupontNitogr | 净利润/营业总收入,反映企业销售获利率 ||
| dupontTaxBurden | 净利润/利润总额,反映企业税负水平,该比值高则税负较低。净利润/利润总额=1-所得税/利润总额 ||
| dupontIntburden | 利润总额/息税前利润,反映企业利息负担,该比值高则税负较低。利润总额/息税前利润=1-利息费用/息税前利润 ||
| dupontEbittogr | 息税前利润/营业总收入,反映企业经营利润率,是企业经营获得的可供全体投资人(股东和债权人)分配的盈利占企业全部营收收入的百分比 ||
| NRTurnRatio | 应收账款周转率(次) | 营业收入/[(期初应收票据及应收账款净额+期末应收票据及应收账款净额)/2] |
| NRTurnDays | 应收账款周转天数(天) | 季报天数/应收账款周转率(一季报:90天,中报:180天,三季报:270天,年报:360天) |
| INVTurnRatio | 存货周转率(次) | 营业成本/[(期初存货净额+期末存货净额)/2] |
| INVTurnDays | 存货周转天数(天) | 季报天数/存货周转率(一季报:90天,中报:180天,三季报:270天,年报:360天) |
| CATurnRatio | 流动资产周转率(次) | 营业总收入/[(期初流动资产+期末流动资产)/2] |
| AssetTurnRatio | 总资产周转率 | 营业总收入/[(期初资产总额+期末资产总额)/2] |
| performanceExpressTotalAsset | 业绩快报总资产 ||
| performanceExpressNetAsset | 业绩快报净资产 ||
| performanceExpressEPSChgPct | 业绩每股收益增长率 ||
| performanceExpressROEWa | 业绩快报净资产收益率ROE-加权 ||
| performanceExpressEPSDiluted | 业绩快报每股收益EPS-摊薄 ||
| performanceExpressGRYOY | 业绩快报营业总收入同比 ||
| performanceExpressOPYOY | 业绩快报营业利润同比 ||
同样我们编写一个脚本来进行读数,脚本代码:
|----|----------------------------------------------------------------------------------------------------------|
| | A |
| 1 | =if(ifv(DATAPATH),DATAPATH,"") |
| 2 | =file(A1/"company.btx") |
| 3 | =cols=if(cols && trim(cols)!="","code,statDate,pubDate,"/cols ) |
| 4 | =cl=if(ifa(cl), cl.sort(),cl) |
| 5 | =if(cl, A2.iselect@b(cl,code;{cols}), A2.cursor@b({cols})) |
| 6 | =if(pos(opt,"p"), "pubDate", "statDate" ) |
| 7 | =if(pos(opt,"y"), "&& month(statDate)==12", "" ) |
| 8 | =ifn(sd, now() ) |
| 9 | =ifn(ed, sd) |
| 10 | =A5.select({A6} \<= A9 {A7} ) |
| 11 | =A5.group(code).(~.select( {A6}\<=A8).maxp( statDate) \| \~.select({A6} >A8 ) ) |
| 12 | =A11.conj(~.sort({A6},statDate).select( {A6}!={A6}\[1\] ).select( statDate\>statDate\[:-1\].max()) ) |
| 13 | =A12.fetch@x().derive().keys@it(code, {A6} ) |
脚本参数:
|------|--------------------------------------------------------------------------------------------|
| opt | 空:按财报统计日期statDate取数;''p":按发布日期pubDate取数;"y"只取年末的财报,因为有时我们需要全年数据,而财报中的数据是按季度累积的,只有年末财报才是全年的 |
| cl | 股票代码序列,如[600000,600001];可以为单值,如600000;也可以填空表示全读 |
| cols | "gpMargin,npMargin"字符串,要读取的字段名,如 |
| sd | 开始日期 |
| ed | 截止日期,如果ed为空,则表示取sd日期下的最新数据。 |
脚本代码保存为loadcompany.splx,此脚本返回开始到截止日期之间的基本面数据。里面有些判断比较复杂,主要是因为有些公司发布财报的日期偶而会错位,更早日期的财报反而更晚发布,结果就需要做比较啰嗦的判断找出某个日期前最新的财报。不过本书中的读数脚本代码都不要求掌握,会用即可,读者可以不关心这些细节。
之所以要按发布日期取数,是因为在做严格策略时,只能引用已经发布过的最新财报数据,财报统计日期已经过了,但该期财报并未发布,这时候也只能引用前一期的财报。
脚本写好后,在init.splx中进行登记。
|-----|---------------------------------------------|
| | A |
| ... | ...... |
| 9 | >register@o("Company", "loadcompany.splx") |
然后就可以调用脚本读取数据了,例如读读取股票000001的基本面数据:
|---|-----------------------------------------------------------------|
| | A |
| 1 | >call("init.splx") |
| 2 | =date(2024,1,1) |
| 3 | =date(2025,1,1) |
| 4 | =Company(1,"gpMargin,npMargin,roeAvg,liabilityToAsset",A2) |
| 5 | =Company(1,"gpMargin,npMargin,roeAvg,liabilityToAsset",A2,A3) |
| 6 | =Company(,,A2,A3) |
| 7 | =Company@y(1,"gpMargin,npMargin,roeAvg,liabilityToAsset",A2,A3) |
| 8 | =Company@p(1,"gpMargin,npMargin,roeAvg,liabilityToAsset",A2,A3) |
A2 开始日期
A3 截止日期
A4 最后一个参数为空,返回2024年1月1日下的最新数据

A5 读取开始和截止日期之间的数据

A6 读取全部股票的所有字段

A7 取年末数据

A8 按发布日期取数

9.2 选股方法
基本面选股方法比较简单,就是筛选出某些财务指标良好的股票。
例如我们希望选出满足以下条件的股票:
-
销售毛利率不低于30%
-
销售净利率不低于15%
-
净资产收益率大于等于20%
-
资产负债率小于50%
编写代码:
|----|-----------------------|---------------|
| | A | B |
| 1 | >call("init.splx") ||
| 2 | =date(2025,1,1) ||
| 3 | =Company@y(,"gpMargin,npMargin,roeAvg,liabilityToAsset",A2) ||
| 4 | gpMargin>=0.3 | 销售毛利率(%) |
| 5 | npMargin>=0.15 | 销售净利率(%) |
| 6 | roeAvg>=0.2 | 净资产收益率(平均)(%) |
| 7 | liabilityToAsset<0.5 | 资产负债率 |
| 8 | =[A4:A7].concat("&&") ||
| 9 | =A3.select(${A8} && statDate>=date(2024,1,1)) ||
| 10 | =A9.id(code) ||
A1:A3 读取数据
A4:A7 设置选股条件
A8 将A4:A7的内容用 && 拼接起来。函数A.concat(d) 表示用分隔符d将序列成员连接成字符串。

A9 筛选出符合条件的股票。${}用来将字符串参数作为表达式使用。
A10 取出符合条件的股票代码。id函数表示取唯一值。

在SPL里去重除了可以用id()函数外,还可用group函数分组去重,例如A10代码也可以写成=A9.group(code;),结果返回序表。注意参数里有分号和无分号的区别。

如果是多年份数据,A10代码则可以写成=A9.group(code,year(statDate):year;),返回符合条件的股票和年份。year函数用来提取日期中的年份。

9.3 基本面相关指标
和指数指标类似,也可以用 EXT 函数将基本面数据拼接到 K 线上。
例如将股票 60000 基本面数据中的流通股本和总股本拼接到 K 线上:
|---|---------|--------------------|
| | A | B |
| 1 | >call("init.splx") ||
| 2 | 2024 | =date(A2,1,1) |
| 3 | =B2-100 | =date(A2,12,31) |
| 4 | 600000 | =Load@C(A4, A3,B3) |
| 5 |
| 6 | =Company@p(A4, null, A3, B3) ||
| 7 | =B4.derive( :流通股,:总股本) ||
| 8 | =EXT@c( A7, A6, "liqaShare:流通股,totalShare:总股本") ||
A8 EXT 加选项 @c 表示拼接的字段来自于公司基本面数据。
计算结果:
