上市公司财务数据异常检测
Lobster
 

之前在回顾一个几个月前参加的某个Kaggle比赛时,翻到了一个非常有趣的异常点检测notebook。链接在这里https://www.kaggle.com/snippsy/feature-importance-over-time-for-outlier-detection

这个方法适用于时间序列数据。

我当时稍微想了想,发现这个方法特么可以推广到财务数据上来啊。然后懒癌的关系又拖了一个多月,今天花了点时间完工了,效果感觉还可以。现在写下来介绍一下。

涉及模型:ExtraTreesRegressor、IsolationForest

涉及平台:米筐量化平台、同花顺问财

算法假设:整一套算法是基于公司每个年份对标签预测的特征重要性是无太大波动的这一假设。

流程简要:在对财务数据预处理后,构建标签,然后按年份将数据扔到ExtraTreesRegressor里面去并输出特征重要性。将所有年份的特征重要性集合起来做成新数据再扔到IsolationForest里面去,最后模型输出哪些年份属于异常年份。

步骤:

以下部分代码属于米筐量化平台才可运行。


一、导包

import pandas as pd
import numpy as np
from sklearn.ensemble import IsolationForest,ExtraTreesRegressor
from tqdm import tqdm

二、导入行业分类数据

这里我选择的是同花顺问财的行业分类数据,感觉比米筐的好些。数据可以从同花顺问财下载。

ind = pd.read_csv('ind.csv',encoding='gbk')[['股票代码','所属同花顺行业']]

三、米筐财务数据字段

Profit = ['revenue','operating_revenue','net_interest_income','net_commission_income','commission_income','commission_expense','net_proxy_security_income','sub_issue_security_income','net_trust_income','earned_premiums','premiums_income','reinsurance_income','reinsurance','unearned_premium_reserve','total_expense','operating_expense','refunded_premiums','compensation_expense','amortization_expense','premium_reserve','amortization_premium_reserve','policy_dividend_payout','reinsurance_cost','other_operating_revenue','other_operating_cost','r_n_d','other_net_income','net_open_hedge_income','other_revenue','gredit_asset_impairment','o_n_a_expense','amortization_reinsurance_cost','insurance_commission_expense','disposal_income_on_asset','cost_of_goods_sold','sales_tax','gross_profit','selling_expense','ga_expense','financing_expense','financing_interest_income','financing_interest_expense','exchange_gains_or_losses','profit_from_operation','invest_income_associates','fair_value_change_income','investment_income','asset_impairment','interest_income','interest_expense','non_operating_revenue','non_operating_expense','disposal_loss_on_asset','other_effecting_total_profits_items','profit_before_tax','income_tax','unrealised_investment_loss','other_effecting_net_profits_items','net_profit','non_recurring_pnl','net_profit_deduct_non_recurring_pnl','classified_by_continuity_operation','continuous_operation_net_profit','discontinued_operation_net_profit','classified_by_ownership','net_profit_parent_company','minority_profit','other_income','other_income_unclassified_income_statement','remearsured_other_income','other_income_equity_unclassified_income_statement','other_equity_instruments_change',
         'corporate_credit_risk_change','other_income_classified_income_statement','other_income_equity_classified_income_statement','financial_asset_available_for_sale_change','financial_asset_hold_to_maturity_change','cash_flow_hedging_effective_portion','foreign_currency_statement_converted_difference','others','other_debt_investment_change','assets_reclassified_other_income','other_debt_investment_reserve','other_income_minority','total_income','total_income_parent_company','total_income_minority','basic_earnings_per_share','fully_diluted_earnings_per_share']
Balnce = ['financial_asset_held_for_trading','cash_equivalent','client_deposits','bill_receivable','dividend_receivable','bill_accts_receivable','interest_receivable','bad_debt_reserve','net_accts_receivable','contract_assets','prepayment','financial_receivable','financial_lease_receivable','prepaid_tax','other_equity_investment','other_illiquidy_financial_assets','non_current_asset_due_one_year','other_receivables_interest_dividend','inventory','consumable_biological_assets','deferred_expense','assets_hold_for_sale','contract_work','other_current_assets','current_assets','financial_asset_available_for_sale','non_current_liability_due_one_year','debt_investment','other_debt_investment','financial_asset_hold_to_maturity','real_estate_investment','long_term_equity_investment','long_term_receivables','net_long_term_equity_investment','accumulated_depreciation','depreciation_reserve','net_fixed_assets','total_fixed_assets','engineer_material','construction_in_progress','total_construction_in_progress','fixed_asset_to_be_disposed','capitalized_biological_assets','oil_and_gas_assets','intangible_assets','seat_costs','impairment_intangible_assets','use_right_assets','goodwill','long_term_deferred_expenses','deferred_income_tax_assets','other_non_current_assets','non_current_assets','loan_account_receivables','fund_providing','reinsurance_reserve_receivable','settlement_provision','client_provision','interbank_deposits','precious_metals','lend_capital','derivative_financial_assets','resale_financial_assets','loans_advances_to_customers','insurance_receivable','subrogation_fee_receivable','reinsurance_receivable','unearned_reserve_receivable','unclaimed_reserve_receivable','life_reserve_receivable','health_reserve_receivable','insurer_mortgage_loan','fixed_deposits','refundable_deposits','refundable_capital_deposits','independent_account_assets','other_assets','other_accts_receivable','total_assets','mortgaged_loan','short_term_loans','financial_liabilities','notes_payable','accts_payable','bill_accts_payable','contract_liabilities','advance_from_customers','payroll_payable','dividend_payable','tax_payable','interest_payable','other_fees_payable','other_payable','other_payable_interest_dividend','short_term_debt','accrued_expense','liabilities_hold_for_sale','estimated_liabilities','deferred_income','long_term_liabilities_due_one_year','other_current_liabilities','current_liabilities','long_term_loans','bond_payable','preference_shares','perpetual_bond','long_term_payable','accrued_staff_costs','grants_received','housing_revolving_funds',
         'deferred_income_tax_liabilities','lease_liabilities','financial_lease_payable','other_non_current_liabilities','non_current_liabilities','borrowings_from_central_banks','deposits_of_interbank','borrowings_capital','derivative_financial_liabilities','buy_back_security_proceeds','deposits','proxy_security_proceeds','sub_issue_security_proceeds','security_deposits_received','advance_insurance','comission_payable','reinsurance_payable','compensation_payable','policy_dividend_payable','deposits_from_interbank','insurance_contract_reserve','insurer_deposit_investment','uncertained_premium_reserve','unclaimed_indemnity_reserve','life_insurance_reserve','health_insurance_reserve','independent_account_liabilities','other_liabilities','provision','deferred_revenue','total_liabilities','paid_in_capital','other_equity_instruments','equity_preferred_stock','perpetual_equity_debt','capital_reserve','surplus_reserve','undistributed_profit','treasury_stock','equity_parent_company','total_equity','general_reserve','trade_risk_allowances','foreign_currency_converted_difference','uncertained_impairment_losses','other_reserves','specific_reserve','minority_interest','total_equity_and_liabilities']
Cash = ['cash_received_from_sales_of_goods','refunds_of_taxes','net_deposit_increase','net_increase_from_central_bank','net_increase_from_other_financial_institutions','draw_back_canceled_loans','cash_received_from_interests_and_commissions','net_increase_from_disposing_financial_assets','net_increase_from_repurchasing_business','cash_received_from_original_insurance','cash_received_from_reinsurance','net_increase_from_insurer_deposit_investment','net_increase_from_financial_institutions','cash_received_from_proxy_security','cash_received_from_sub_issue_security','cash_from_other_operating_activities','cash_from_operating_activities','cash_paid_for_goods_and_services','assets_depreciation_reserves','exchange_rate_change_effect','other_effecting_cash_equivalent_items','cash_equivalent_increase','begin_period_cash_equivalent','end_period_cash_equivalent','cash_paid_for_employee','cash_paid_for_taxes','net_increase_from_loans_and_advances','net_increase_from_central_bank_and_banks','net_increase_from_lending_capital','cash_paid_for_comissions','cash_paid_for_orignal_insurance','cash_paid_for_reinsurance','cash_paid_for_policy_dividends','net_increase_from_trading_financial_assets','net_increase_from_operating_buy_back',
       'cash_paid_for_other_operation_activities','cash_paid_for_operation_activities','cash_flow_from_operating_activities','cash_received_from_disposal_of_investment','cash_received_from_investment','cash_received_from_disposal_of_asset','cash_received_from_other_investment_activities','cash_received_from_investment_activities','cash_paid_for_asset','cash_paid_to_acquire_investment','cash_paid_for_other_investment_activities','cash_paid_for_investment_activities','cash_flow_from_investing_activities','cash_received_from_investors','cash_received_from_minority_invest_subsidiaries','cash_received_from_issuing_security','cash_received_from_financial_institution_borrows','cash_received_from_issuing_equity_instruments','net_increase_from__financing_buy_back','cash_received_from_other_financing_activities','cash_received_from_financing_activities','cash_paid_for_debt','cash_paid_for_dividend_and_interest','dividends_paid_to_minority_by_subsidiaries','cash_paid_for_other_financing_activities','cash_paid_to_financing_activities','cash_flow_from_financing_activities','net_cash_deal_from_sub','net_cash_payment_from_sub','net_increase_in_pledge_loans','net_increase_from_investing_buy_back','net_inc_cash_and_equivalents','fixed_asset_depreciation','deferred_expense_amortization','intangible_asset_amortization']

四、按财报时间读取数据、预处理、构建标签

1、读取数据

年报公布时间最晚是在每年的4月30日,因此我们可以将当年5月1日到次年4月30日作为一个年份。

2、数据预处理

由于财报数据对模型来说太少了,所以要想办法扩容。最好的办法就是仿照市盈率公式,用公司总市值/各个财报科目。对于NaN和inf值,均用0填充。

(米筐对每个财报因子都是输出当季度的值。比如一个财报年,米筐会输出四个值。)

3.构建标签

这是一个可以优化的地方,如果想到有更好的标签可以有更好的模型效果。我目前用的是【每日目标股票的涨跌幅-每日目标股票所属行业的所有股票的涨跌幅均值】。以此来衡量当日目标股票偏离其所属行业多少。ExtraTreesRegressor模型任务是预测次日偏离值。

code = '600077.XSHG' #这里填股票代码
year = range(2010,2021,1)
st = '0501'
ed = '0430'
industry = ind.loc[ind['股票代码']==Sstr(code),'所属同花顺行业'].values[0]
ind_stock = ind.loc[ind['所属同花顺行业']==industry,'股票代码'].apply(Bstr)
new_data = pd.DataFrame()
for y in year:
    start = str(y)+st
    end = str(y+1)+ed
    label = get_price_change_rate(code,start,end)-get_price_change_rate(ind_stock,start,end).mean(axis=1)
    pro = get_factor(code,Profit,start,end).fillna(0)
    bal = get_factor(code,Balnce,start,end).fillna(0)
    cas = get_factor(code,Cash,start,end).fillna(0)
    temp_data = pd.concat([pro,bal,cas],axis=1).reset_index()
    temp_data = temp_data.drop(['order_book_id','date'],axis=1)
    cap = get_factor(code,'market_cap_3',start,end).fillna(0).to_numpy()
    temp_data = pd.DataFrame(np.nan_to_num(temp_data.apply(lambda x: cap/x,axis=0),posinf=0,neginf=0),columns=temp_data.columns)
    temp_data['date'] = y
    temp_data['label'] = label.values
    temp_data['label'] =temp_data['label'].shift(-1)
    temp_data = temp_data.dropna(axis=0,how='any')
    new_data = new_data.append(temp_data)

五、模型训练

这一步主要是要按不同年份进行训练(每个年份都会训练一个模型),同时将整个算法运行十次,最后将异常年份进行出现次数的统计。

time_list = new_data['date'].drop_duplicates().to_list()
fea_imp = []
res = pd.Series()
tt = 0
while tt<10:
    for t in tqdm(time_list):
        new = new_data.loc[new_data['date']==t,:].copy()
        new = new.drop('date',axis=1)
        lab = new.pop('label')
        etc = ExtraTreesRegressor()
        etc.fit(new,lab)
        fea_imp.append(etc.feature_importances_)
    feature_importance = np.asarray(fea_imp)
    pred = IsolationForest().fit_predict(feature_importance)
    outlier_days = [day for day,pred in zip(time_list,pred) if pred < 0]
    res = res.append(pd.Series(outlier_days))
    fea_imp = []
    tt+=1
print(res.value_counts())

六、实践

我们用这篇文章里的部分公司进行试验:https://www.wogoo.com/essay/#/forwarding?articleId=e08334e42a1a40f79b408c9a1fd8d93b&articleType=0

时间从2010年-2020年。

$*ST金正(002470.SZ)$ 

2020    10
2019     6
2017     3
2010     3
dtype: int64

$宋都股份(600077.SH)$ 

2011    10
2018     7
2020     6
2019     3
2010     3
dtype: int64

$康普顿(603798.SH)$ (康普顿16年上市,所以开始日期为20160501)

2020    2
2019    2
dtype: int64

$万润股份(002643.SZ)$ (万润股份11年底上市,所以开始日期为20120501)

2020    10
2014     6
2015     4
2016     1
dtype: int64

七、算法缺陷和能改进的地方

1.这个算法不能辨别某年份的财报异常是属于积极的还是消极的。

2.标签的构建可能存在更好的选择。

3.米筐不能提供一些更详细的财务数据,理论上说,更好的财务特征能够带来更好的提升。

点赞0
收藏3
分享
举报
评论 0
表情
x
😊😍😘😳😡😓😭😲😁😱😖😉😏😜😰😢😚😄😪😣😔😠😌😝😂😥😃😨😒😷😞👿👽😁😄😇😯😕😂😅😈😐😠😀😃😆😉😑😬😮😥😨😟😢😣😦😩😱😵😴😤😧😰😶😷😝😙😎😖😞😛😋😭😔😒😜😗😚😌😪😏🙋🙅🙎😼😻🙌🙆🙏😸😽😫🙍🙇😁😄😇😯😕😂😅😈😐😠😀😃😆😉😑😬😮😥😨😟😢😣😦😩😱😵😴😤😧😰😶😷😝😙😎😖😞😛😋😭😔😒😜😗😚😌😪😏🙋🙅🙎😼😻🙌🙆🙏😸😽😫🙍🙇😺😹😿😾🙉👶👨👵🙀🙊👦👩😄😃😀😉😚😗😙😜😝😛😁😔😌😒😞😣😢😂😭😪😥😰😅😩😫😨😱😠😤😖😆😋😷😎😴😵😟😦😧😈👿😮😬😐😕😯😶😇😏😑👲👳👮👷💂👶👦👧👨👩👴👵👱👼👸😺😸😻😽😼🙀😿😹😾👹👺🙈🙉🙊💀👽👀👃👄👂💔💘💝💜💛💚💙💩👍👎👊👌💪👆👇👈👉👐🙏🙌👏👧👦👩👨👶👵👴👳👳👳👲👸👸👷💂👮🙆🙅💇🙅💇💆💁💁👯👫👫🎎🚶🏃💃💑💏👼💀🐱🐶🐭🐹🐰🐺🐸🐯🐨🐻🐷🐮🐗🐵🐙🐛🐔🐧🐦🐍🐴🐠🐳🐬🌙🌊🌻🌺🌹🔥🎵💦💤🌷🌸💐🍀🌾🍃🍂🎃👻🎅🌵🌴🎍🍁🎄🔔🎉🎈💿📷🎥📬💡🔑🔒🔓📺💻🛀💰🔫💊💣🏈🏀🎾🎿🏄🏊🏆👾🎤🎸👙👑🌂👜💄💅💍🎁💎🎂🍰🍺🍻🍸🍵🍶🍔🍟🍝🍜🍧🍦🍡🍙🍘🍞🍛🍚🍲🍱🍣🍎🍓🍉🍆🍅🍊🚀🚄🚉🚃🚗🚕🚓🚒🚑🚙🚲🏁🚹🚺😺😹😿😾🙉👶👨👵🙀🙊👦👩💏🙈💩👧👴💑👪👫👬👭👮💂👸👱💃👤👷👯🎅👲💆👥💁👰👼👳💇💅👺👿👀👣💋👻👽💀👂👄👹👾💪👃👅💙💚💓💖💝👍💛💔💗💞👎💜💕💘💟👌👊👇👋👈👏👆👉👐🔰👟🎩👖👙💄👑🎓👔👗👠👞👒👓👕👘👡👢💼👛💲💶💱👚🎒💰💵💷💹👜👝💳💴💸🔫🔪💊🔕🔭🔋📗💣🚬🚪🔮🔌📘💉🔔🔬🔦📜📙📚📑📖🎃🎁🎆📔📓📰🎄🎂🎇📒📕📛🎀🎈🎉🎊🎌🎎📟📠📨🎍🎐📱📦📩🎏🎋📲📞📪📫📮📯📡📏📭📤📢💬📐📬📥📣💭📝📍📌💺💾📅📁📄📎💻💿📇📂📊💽📆📋📃📈📉🎢🎨📷🎭🎲🎠🎬📹🎫🎰🎡🎪🎥🎦🎮🃏🎴📺📼🎵🎻🎺🀄📻🎧🎶🎹🎸🎯📀🎤🎼🎷🐕🐈🐁🐢🐓🐤🐶🐱🐭🐇🐔🐥🐩🐀🐹🐰🐣🐦🐏🐺🐄🐗🐽🐼🐑🐃🐮🐖🐸🐧🐐🐂🐴🐷🐍🐘🐨🐆🐫🐳🐠🐚🐒🐯🐪🐋🐡🐬🐵🐻🐊🐟🐙🐌🐛🐞🐾🍻🍶🍼🐜🐲🍸🍷🍴🐝🐉🍺🍹🍵🍨🍧🍰🍬🍯🍟🍖🍦🍪🍭🍳🍝🍗🍩🍫🍮🍔🍕🍤🍣🍜🍛🍢🍠🍏🍱🍙🍲🍡🍌🍊🍞🍚🍥🍘🍎🍋🍄🍇🍐🍓🌴🌴🍅🍈🍑🍍🌲🌵🍆🍉🍒🌰🌳🌷🌸🍁🌺🌽🌹🍂🌻🌾🌈🌁🍀🍃🌼🌿🌂🌀🌙🌚🌑🌔💧🌞🌛🌒🌕🌝🌜🌓🌖🌗🌄🌆🌉🌎🌐🌘🌅🌃🌊🌏🌟🎑🌇🌌🌋🌍🌠🏠🏣🏦🏩🏯🏡🏤🏧🏪🏰🏢🏥🏨🏫🏬🏭🗻🗾🏮🔨🛁🚾🗼🗿💈🔩🛀🎽🗽🔧🚿🚽🎣🎱🎿🏂🏂🏆🏈🎳🎾🏀🏃🏇🏉🏁🏄🐎🏊🚂🚅🚋🚎🚑🚃🚆🚈🚌🚏🚒🚄🚇🚊🚍🚐🚓🚔🚗🚚🚝🚠🚣🚕🚘🚛🚞🚡🚁🚖🚙🚜🚢🚢🛂🛅🚳🚷🚀🛃🚴🚸🚤🅿🛄🚲🚵🚉🚶🚥🚦💎🚧💌💐🚨💍💒💏🙈💩👧👴💑👪👫👬👭👮💂👸👱💃👤👷👯🎅👲💆👥💁👰👼👳💇💅👺👿👀👣💋👻👽💀👂👄👹👾💪👃👅💙💚💓💖💝👍💛💔💗💞👎💜💕💘💟👌👊👇👋👈👏👆👉👐🔰👟🎩👖👙💄👑🎓👔👗👠👞👒👓👕👘👡👢💼👛💲💶💱👚🎒💰💵💷💹👜👝💳💴💸🔫🔪💊🔕🔭🔋📗💣🚬🚪🔮🔌📘💉🔔🔬🔦📜📙📚📑📖🎃🎁🎆📔📓📰🎄🎂🎇📒📕📛🎀🎈🎉🎊🎌🎎📟📠📨🎍🎐📱📦📩🎏🎋📲📞📪📫📮📯📡📏📭📤📢💬📐📬📥📣💭📝📍📌💺💾📅📁📄📎💻💿📇📂📊💽📆📋📃📈📉🎢🎨📷🎭🎲🎠🎬📹🎫🎰🎡🎪🎥🎦🎮🃏🎴📺📼🎵🎻🎺🀄📻🎧🎶🎹🎸🎯📀🎤🎼🎷🐕🐈🐁🐢🐓🐤🐶🐱🐭🐇🐔🐥🐩🐀🐹🐰🐣🐦🐏🐺🐄🐗🐽🐼🐑🐃🐮🐖🐸🐧🐐🐂🐴🐷🐍🐘🐨🐆🐫🐳🐠🐚🐒🐯🐪🐋🐡🐬🐵🐻🐊🐟🐙🐌🐛🐞🐾🍻🍶🍼🐜🐲🍸🍷🍴🐝🐉🍺🍹🍵🍨🍧🍰🍬🍯🍟🍖🍦🍪🍭🍳🍝🍗🍩🍫💩🔥🌟💫💥💢💦💧💤💨👂👀👃👅👄👍👎👌👊👋👐👆👇👉👈🙌🙏👏💪🚶🏃💃👫👪👬👭💏💑👯🙆🙅💁🙋💆💇💅👰🙎🙍🙇🎩👑👒👟👞👡👠👢👕👔👚👗🎽👖👘👙💼👜👝👛👓🎀🌂💄💛💙💜💚💔💗💓💕💖💞💘💌💋💍💎👤👥💬👣💭🐶🐺🐱🐭🐹🐰🐸🐯🐨🐻🐷🐽🐮🐗🐵🐒🐴🐑🐘🐼🐧🐦🐤🐥🐣🐔🐍🐢🐛🐝🐜🐞🐌🐙🐚🐠🐟🐬🐳🐋🐄🐏🐀🐃🐅🐇🐉🐎🐐🐓🐕🐖🐁🐂🐲🐡🐊🐫🐪🐆🐈🐩🐾💐🌸🌷🍀🌹🌻🌺🍁🍃🍂🌿🌾🍄🌵🌴🌲🌳🌰🌱🌼🌐🌞🌝🌚🌑🌒🌓🌔🌕🌖🌗🌘🌜🌛🌙🌍🌎🌏🌋🌌🌠🌀🌁🌈🌊🎍💝🎎🎒🎓🎏🎆🎇🎐🎑🎃👻🎅🎄🎁🎋🎉🎊🎈🎌🔮🎥📷📹📼💿📀💽💾💻📱📞📟📠📡📺📻🔊🔉🔈🔇🔔🔕📢📣🔓🔒🔏🔐🔑🔎💡🔦🔆🔅🔌🔋🔍🛁🛀🚿🚽🔧🔩🔨🚪🚬💣🔫🔪💊💉💰💴💵💷💶💳💸📲📧📥📤📩📨📯📫📪📬📭📮📦📝📄📃📑📊📈📉📜📋📅📆📇📁📂📌📎📏📐📕📗📘📙📓📔📒📚📖🔖📛🔬🔭📰🎨🎬🎤🎧🎼🎵🎶🎹🎻🎺🎷🎸👾🎮🃏🎴🀄🎲🎯🏈🏀🎾🎱🏉🎳🚵🚴🏁🏇🏆🎿🏂🏊🏄🎣🍵🍶🍼🍺🍻🍸🍹🍷🍴🍕🍔🍟🍗🍖🍝🍛🍤🍱🍣🍥🍙🍘🍚🍜🍲🍢🍡🍳🍞🍩🍮🍦🍨🍧🎂🍰🍪🍫🍬🍭🍯🍎🍏🍊🍋🍒🍇🍉🍓🍑🍈🍌🍐🍍🍠🍆🍅🌽🏠🏡🏫🏢🏣🏥🏦🏪🏩🏨💒🏬🏤🌇🌆🏯🏰🏭🗼🗾🗻🌄🌅🌃🗽🌉🎠🎡🎢🚢🚤🚣🚀💺🚁🚂🚊🚉🚞🚆🚄🚅🚈🚇🚝🚋🚃🚎🚌🚍🚙🚘🚗🚕🚖🚛🚚🚨🚓🚔🚒🚑🚐🚲🚡🚟🚠🚜💈🚏🎫🚦🚥🚧🔰🏮🎰🗿🎪🎭📍🚩
表情
取消 发布
评论  0