Skip to content

Commit

Permalink
Merge branch 'master' of github.com:zmcx16/Norn-StockScreener
Browse files Browse the repository at this point in the history
  • Loading branch information
zmcx16 committed Feb 27, 2024
2 parents b19a5a3 + faaedd1 commit 37cfcf5
Show file tree
Hide file tree
Showing 6 changed files with 428 additions and 3 deletions.
6 changes: 5 additions & 1 deletion src/common/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const NornFinanceAPIServerGithub = "https://github.com/zmcx16/Norn-Financ
export const ShortSummaryRelLink = "/short-stocks-summary/"
export const ESGLink = 'https://www.sustainalytics.com/esg-ratings'
export const DividendChampionsUrl = "https://moneyzine.com/investments/dividend-champions/"
export const BenfordLawUrl = "https://www.learndatasci.com/glossary/benfords-law/"

export const argSetValueBackgroundColor = 'rgba(144,238,144, 0.5)'

Expand All @@ -37,6 +38,7 @@ export const pageRouterTable = [
{ text: 'Short Stocks Summary', path: '/short-stocks-summary/' },
{ text: 'Dividend Champions', path: '/dividend-champions/' },
{ text: 'ESG Stocks Summary', path: '/esg-stocks-summary/' },
{ text: "Stock Benford's Law", path: '/stock-benford-law/' },
{ text: 'Stock Price Simulation', path: '/stock-price-simulation/' },
{ text: 'Market Correlation Matrix', path: '/market-correlation-matrix/' },
]
Expand Down Expand Up @@ -69,4 +71,6 @@ The idea for the spreadsheet was created in 2008 by Dave Fish (deceased in 2018)
and is now available and updated every Friday afternoon at Dividend Radar.`
export const DividendDRGDescription =
`Dividend Growth: CAGRs are calculated using trailing twelve months`
export const PCRTooltip = `Put-Call Ratio (PCR) Data Source: Yahoo Finance (Data range: 365 days)`
export const PCRTooltip = `Put-Call Ratio (PCR) Data Source: Yahoo Finance (Data range: 365 days)`
export const BenfordLawTooltip = `Benford's Law, a statistical phenomenon, asserts that in various datasets, smaller first digits (1, 2, 3) occur more frequently than larger ones. Applied to financial data, it can aid in detecting anomalies and potential fraud.
While Benford's Law is a valuable tool, it is not foolproof. It serves as a red flag, prompting further scrutiny rather than offering definitive proof of fraud. Used alongside other forensic accounting techniques, it enhances the effectiveness of financial statement analysis.`
6 changes: 4 additions & 2 deletions src/common/dataGridUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ import { convertKMBT } from './utils'
export const YahooFinanceUrl = YahooFinanceEnUrl


export function PureFieldWithValueCheck(field, headerName, width, valueFixed, hide, description = null) {
export function PureFieldWithValueCheck(field, headerName, width, valueFixed, hide, description = null, toExponential = false) {
let output = {
field: field,
headerName: headerName,
width: width,
type: 'number',
renderCell: (params) => (
params.value === "-" || params.value === -Number.MAX_VALUE || params.value === Number.MAX_VALUE || params.value === null || params.value === undefined || params.value === "Infinity" || params.value === 'NaN' ?
<span>-</span> :
<span>-</span> :
toExponential ?
<span>{params.value.toExponential(valueFixed)}</span> :
<span>{params.value.toFixed(valueFixed)}</span>
),
hide: hide
Expand Down
274 changes: 274 additions & 0 deletions src/components/stock-benford-law/stockBenfordLaw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@

import React, { useState, useRef, useEffect } from 'react'
import { DataGrid } from '@mui/x-data-grid'
import IconButton from '@mui/material/IconButton'
import BarChartSharpIcon from '@mui/icons-material/BarChartSharp'
import useFetch from 'use-http'
import moment from 'moment'
import Link from '@mui/material/Link'

import StockBenfordLawChart from './stockBenfordLawChart'
import ModalWindow from '../modalWindow'
import DefaultDataGridTable from '../defaultDataGridTable'
import SearchGridToolbar from '../searchGridToolbar'
import { BenfordLawTooltip, BenfordLawUrl} from '../../common/common'
import { SymbolNameField, PriceField, PureFieldWithValueCheck, PercentField, ColorPercentField } from '../../common/dataGridUtil'

import stockBenfordLawStyle from './stockBenfordLaw.module.scss'
import '../muiTablePagination.css'

const StockBenfordLaw = ({ loadingAnimeRef }) => {

const [hideColState, setHideColState] = useState({})

const modalWindowRef = useRef({
popModalWindow: null,
popPureModal: null,
})


const tableColList = {
Close: { hide: false, text: 'Price' },
LastQuarterSSE: { hide: false, text: 'LastQ SSE' },
LastYearSSE: { hide: false, text: 'LastY SSE' },
AllQuartersSSE: { hide: false, text: 'AllQ SSE' },
AllYearsSSE: { hide: false, text: 'AllY SSE' },
AllQuartersYearsSSE: { hide: false, text: 'AllQY SSE' },
PE: { hide: false, text: 'P/E' },
PB: { hide: false, text: 'P/B' },
Dividend: { hide: false, text: 'Dividend %' },
High52: { hide: false, text: '52W High' },
Low52: { hide: false, text: '52W Low' },
PerfWeek: { hide: false, text: 'Perf Week' },
PerfMonth: { hide: false, text: 'Perf Month' },
PerfQuarter: { hide: false, text: 'Perf Quarter' },
PerfHalfY: { hide: false, text: 'Perf Half Y' },
PerfYear: { hide: false, text: 'Perf Year' },
PerfYTD: { hide: false, text: 'Perf YTD' },
Chart: { hide: false, text: 'Chart' },
}

const getData = async (url, fetchObj) => {
const resp_data = await fetchObj.get(url)
if (fetchObj.response.ok && resp_data) {
return resp_data
}
else {
return null
}
}

const renderShowChart = (symbol, chartData, SSEData)=> {
modalWindowRef.current.popModalWindow(<StockBenfordLawChart title={`${symbol} Chart`} chartData={chartData} SSEData={SSEData}/>)
}

const genTableColTemplate = () => {
return [
SymbolNameField('Symbol', 130, 'symbol' in hideColState ? hideColState['symbol'] : false),
PriceField('close', tableColList.Close.text, 110, 'close' in hideColState ? hideColState['close'] : tableColList['Close'].hide, null, "yahoo"),
PureFieldWithValueCheck("lastQuarterSSE", tableColList.LastQuarterSSE.text, 110, 3, "lastQuarterSSE" in hideColState ? hideColState["lastQuarterSSE"] : tableColList['LastQuarterSSE'].hide, "The sum of squares due to error (SSE) for Benford's Law in the last quarter's financial data.", true),
PureFieldWithValueCheck("lastYearSSE", tableColList.LastYearSSE.text, 110, 3, "lastYearSSE" in hideColState ? hideColState["lastYearSSE"] : tableColList['LastYearSSE'].hide, "The sum of squares due to error (SSE) for Benford's Law in the last year's financial data.", true),
PureFieldWithValueCheck("allQuartersSSE", tableColList.AllQuartersSSE.text, 110, 3, "allQuartersSSE" in hideColState ? hideColState["allQuartersSSE"] : tableColList['AllQuartersSSE'].hide, "The sum of squares due to error (SSE) for Benford's Law in all quarters' financial data.", true),
PureFieldWithValueCheck("allYearsSSE", tableColList.AllYearsSSE.text, 110, 3, "allYearsSSE" in hideColState ? hideColState["allYearsSSE"] : tableColList['AllYearsSSE'].hide, "The sum of squares due to error (SSE) for Benford's Law in all years' financial data.", true),
PureFieldWithValueCheck("allQuartersYearsSSE", tableColList.AllQuartersYearsSSE.text, 110, 3, "allQuartersYearsSSE" in hideColState ? hideColState["allQuartersYearsSSE"] : tableColList['AllQuartersYearsSSE'].hide, "The sum of squares due to error (SSE) for Benford's Law in all quarters and years' financial data.", true),
PureFieldWithValueCheck("PE", tableColList.PE.text, 110, 2, "PE" in hideColState ? hideColState["PE"] : tableColList['PE'].hide),
PureFieldWithValueCheck("PB", tableColList.PB.text, 110, 2, "PB" in hideColState ? hideColState["PB"] : tableColList['PB'].hide),
PercentField("dividend", tableColList.Dividend.text, 150, "dividend" in hideColState ? hideColState["dividend"] : tableColList['Dividend'].hide),
PercentField("high52", tableColList.High52.text, 150, "high52" in hideColState ? hideColState["high52"] : tableColList['High52'].hide),
PercentField("low52", tableColList.Low52.text, 150, "low52" in hideColState ? hideColState["low52"] : tableColList['Low52'].hide),
ColorPercentField("perfWeek", tableColList.PerfWeek.text, 150, 2, "perfWeek" in hideColState ? hideColState["perfWeek"] : tableColList['PerfWeek'].hide, 500),
ColorPercentField("perfMonth", tableColList.PerfMonth.text, 150, 2, "perfMonth" in hideColState ? hideColState["perfMonth"] : tableColList['PerfMonth'].hide, 500),
ColorPercentField("perfQuarter", tableColList.PerfQuarter.text, 150, 2, "perfQuarter" in hideColState ? hideColState["perfQuarter"] : tableColList['PerfQuarter'].hide, 500),
ColorPercentField("perfHalfY", tableColList.PerfHalfY.text, 150, 2, "perfHalfY" in hideColState ? hideColState["perfHalfY"] : tableColList['PerfHalfY'].hide, 500),
ColorPercentField("perfYear", tableColList.PerfYear.text, 150, 2, "perfYear" in hideColState ? hideColState["perfYear"] : tableColList['PerfYear'].hide, 500),
ColorPercentField("perfYTD", tableColList.PerfYTD.text, 150, 2, "perfYTD" in hideColState ? hideColState["perfYTD"] : tableColList['PerfYTD'].hide, 500),
{
field: 'Chart',
headerName: tableColList.Chart.text,
width: 130,
renderCell: (params) => (
<IconButton
size="small"
aria-haspopup="true"
onClick={()=>{
renderShowChart(params.row["symbol"], params.row["chartData"], params.row["SSEData"])
}}
>
<BarChartSharpIcon color="primary" style={{ fontSize: 40 }} />
</IconButton>
),
hide: 'Chart' in hideColState ? hideColState['Chart'] : tableColList['Chart'].hide
},
]
}

const fetchStockData = useFetch({ cachePolicy: 'no-cache' })
const fetchBenfordData = useFetch({ cachePolicy: 'no-cache' })
const renderStockBenfordLawTable = (config, showChart)=>{
Promise.all([
getData("/norn-data/stock/stat.json", fetchStockData),
getData('/norn-data/stock-benford-law.json', fetchBenfordData),
]).then((allResponses) => {
//console.log(allResponses)
if (allResponses.length === 2 && allResponses[0] !== null && allResponses[1] !== null) {
let benfordProbs = allResponses[1]["benfordDigitProbs"]
let output = Object.keys(allResponses[1]["data"]).reduce((result, symbol, index) => {
let stockInfo = allResponses[0][symbol]
let benfordData = allResponses[1]["data"][symbol]["data"]["stockDigitProbsSSE"]
let lastQuarterSSE = "benfordSSE" in benfordData["lastQuarter"] ? benfordData["lastQuarter"]["benfordSSE"] : undefined
let lastYearSSE = "benfordSSE" in benfordData["lastYear"] ? benfordData["lastYear"]["benfordSSE"] : undefined
let allQuartersSSE = "benfordSSE" in benfordData["allQuarters"] ? benfordData["allQuarters"]["benfordSSE"] : undefined
let allYearsSSE = "benfordSSE" in benfordData["allYears"] ? benfordData["allYears"]["benfordSSE"] : undefined
let allQuartersYearsSSE = "benfordSSE" in benfordData["allQuartersYears"] ? benfordData["allQuartersYears"]["benfordSSE"] : undefined

const genChartData = (d, valid) => {
let chartData = []
for (let i = 0; i < 9; i++) {
chartData.push({
name: `${i + 1}`,
dataProbs: valid ? parseInt(d["prob"][i] * 10000, 10) / 100.0 : 0,
benfordProbs: parseInt(benfordProbs[i] * 10000, 10) / 100.0
})
}
return chartData
}
const genChartTitle = (title, d, sse) => {
let n = "No Data"
let sseText = "N/A"
if (sse !== undefined) {
n = d.reduce((a,b)=>a+b)
sseText = sse.toExponential(3)
}
return `[${title}] n=${n}, SSE=${sseText}`
}

let chartData = {
"lastQuarter" : {
"data": genChartData(benfordData["lastQuarter"], lastQuarterSSE !== undefined),
"title": genChartTitle("Last Quarter", benfordData["lastQuarter"]["count"], lastQuarterSSE)
},
"lastYear": {
"data": genChartData(benfordData["lastYear"], lastYearSSE !== undefined),
"title": genChartTitle("Last Year", benfordData["lastYear"]["count"], lastYearSSE)
},
"allQuarters": {
"data": genChartData(benfordData["allQuarters"], allQuartersSSE !== undefined),
"title": genChartTitle("All Quarters", benfordData["allQuarters"]["count"], allQuartersSSE)
},
"allYears": {
"data": genChartData(benfordData["allYears"], allYearsSSE !== undefined),
"title": genChartTitle("All Years", benfordData["allYears"]["count"], allYearsSSE)
},
"allQuartersYears": {
"data": genChartData(benfordData["allQuartersYears"], allQuartersYearsSSE !== undefined),
"title": genChartTitle("All Q & Y", benfordData["allQuartersYears"]["count"], allQuartersYearsSSE)
},
}

let SSEData = [
{
"name": "Financial Statement SSE",
"lastQuarterSSE": lastQuarterSSE !== undefined ? lastQuarterSSE.toExponential(3) : 0,
"lastYearSSE": lastYearSSE !== undefined ? lastYearSSE.toExponential(3) : 0,
"allQuartersSSE": allQuartersSSE !== undefined ? allQuartersSSE.toExponential(3) : 0,
"allYearsSSE": allYearsSSE !== undefined ? allYearsSSE.toExponential(3) : 0,
"allQuartersYearsSSE": allQuartersYearsSSE !== undefined ? allQuartersYearsSSE.toExponential(3) : 0,
},
]
console.log(SSEData)
let o = {
id: index,
symbol: symbol,
close: stockInfo !== undefined && stockInfo !== null && stockInfo['Close'] !== '-' ? stockInfo['Close'] : -Number.MAX_VALUE,
lastQuarterSSE: lastQuarterSSE !== undefined && lastQuarterSSE !== null ? lastQuarterSSE : -Number.MAX_VALUE,
lastYearSSE: lastYearSSE !== undefined && lastYearSSE !== null ? lastYearSSE : -Number.MAX_VALUE,
allQuartersSSE: allQuartersSSE !== undefined && allQuartersSSE !== null ? allQuartersSSE : -Number.MAX_VALUE,
allYearsSSE: allYearsSSE !== undefined && allYearsSSE !== null ? allYearsSSE : -Number.MAX_VALUE,
allQuartersYearsSSE: allQuartersYearsSSE !== undefined && allQuartersYearsSSE !== null ? allQuartersYearsSSE : -Number.MAX_VALUE,
PE: stockInfo !== undefined && stockInfo !== null && stockInfo['P/E'] !== '-' ? stockInfo['P/E'] : Number.MAX_VALUE,
PB: stockInfo !== undefined && stockInfo !== null && stockInfo['P/B'] !== '-' ? stockInfo['P/B'] : Number.MAX_VALUE,
dividend: stockInfo !== undefined && stockInfo !== null && stockInfo['Dividend %'] !== '-' ? stockInfo['Dividend %'] : -Number.MAX_VALUE,
high52: stockInfo !== undefined && stockInfo !== null && stockInfo['52W High'] !== '-' ? stockInfo['52W High'] : -Number.MAX_VALUE,
low52: stockInfo !== undefined && stockInfo !== null && stockInfo['52W Low'] !== '-' ? stockInfo['52W Low'] : -Number.MAX_VALUE,
perfWeek: stockInfo !== undefined && stockInfo !== null && stockInfo['Perf Week'] !== '-' ? stockInfo['Perf Week'] : -Number.MAX_VALUE,
perfMonth: stockInfo !== undefined && stockInfo !== null && stockInfo['Perf Month'] !== '-' ? stockInfo['Perf Month'] : -Number.MAX_VALUE,
perfQuarter: stockInfo !== undefined && stockInfo !== null && stockInfo['Perf Quarter'] !== '-' ? stockInfo['Perf Quarter'] : -Number.MAX_VALUE,
perfHalfY: stockInfo !== undefined && stockInfo !== null && stockInfo['Perf Half Y'] !== '-' ? stockInfo['Perf Half Y'] : -Number.MAX_VALUE,
perfYear: stockInfo !== undefined && stockInfo !== null && stockInfo['Perf Year'] !== '-' ? stockInfo['Perf Year'] : -Number.MAX_VALUE,
perfYTD: stockInfo !== undefined && stockInfo !== null && stockInfo['Perf YTD'] !== '-' ? stockInfo['Perf YTD'] : -Number.MAX_VALUE,
chartData: chartData,
SSEData: SSEData,
}

if(config.filter_symbols.length === 0 || config.filter_symbols.includes(symbol)) {
result.push(o)
}
return result
}, [])
console.log(output)
setRowData(output)
if (showChart) {
renderShowChart(output[0]["symbol"], output[0]["chartData"], output[0]["SSEData"])
}
} else {
modalWindowRef.current.popModalWindow(<div>Load some data failed</div>)
}
loadingAnimeRef.current.setLoading(false)
}).catch((error) => {
console.error(error)
modalWindowRef.current.popModalWindow(<div>Can't get data</div>)
loadingAnimeRef.current.setLoading(false)
})
}

const [rowData, setRowData] = useState([])
const [searchVal, setSearchVal] = useState("")
useEffect(() => {
// componentDidMount is here!
// componentDidUpdate is here!
let config = {filter_symbols: []}
let showChart = false
if (typeof window !== 'undefined') {
const queryParameters = new URLSearchParams(window.location.search)
let symbol = queryParameters.get("symbol")
if (symbol) {
config = {filter_symbols: [symbol]}
}
showChart = queryParameters.get("showChart") === "true"
}
renderStockBenfordLawTable(config, showChart)
return () => {
// componentWillUnmount is here!
}
}, [])

return (
<>
<div className={stockBenfordLawStyle.container}>
<div className={stockBenfordLawStyle.table}>
<DataGrid rows={rowData} columns={genTableColTemplate()} components={{ NoRowsOverlay: DefaultDataGridTable, Toolbar: ()=>{
return <SearchGridToolbar searchVal={searchVal} setSearchVal={setSearchVal} clickCallback={(config)=>{
renderStockBenfordLawTable(config, false)
}}
info={{
placeholder: 'Filter symbols: AAPL, BAC, KSS, ...',
tooltip: {
text: BenfordLawTooltip,
link: BenfordLawUrl
}
}}
/>
}}} disableSelectionOnClick onColumnVisibilityChange={(param) => {
let tempHideColState = hideColState
tempHideColState[param['field']] = !param['isVisible']
setHideColState(tempHideColState)
}}
/>
</div>
</div>
<ModalWindow modalWindowRef={modalWindowRef} />
</>
)
}

export default StockBenfordLaw
15 changes: 15 additions & 0 deletions src/components/stock-benford-law/stockBenfordLaw.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.container {
padding: 10px 0;
width: 100%;
}

.showColumn {
border: 1px solid silver;
border-radius: 4px;
padding: 0px 10px;
margin: 0 1px 10px 1px;
}

.table{
height: 800px;
}
Loading

0 comments on commit 37cfcf5

Please sign in to comment.