Skip to content

Commit e221bac

Browse files
authored
Merge pull request #55 from InserToken/feat/1-auth/eundong
[feat] per, pbr, psr 계산 로직 수정
2 parents 0ea3b43 + d0c9000 commit e221bac

File tree

3 files changed

+108
-36
lines changed

3 files changed

+108
-36
lines changed

app.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ app.use("/api/rank", rankRouter);
7979
app.use("/api/practicescores", practiceScoreRouter);
8080
const fetchFinancialData = require("./routes/financialRoutes");
8181
app.use("/api", fetchFinancialData);
82+
app.use("/api", require("./routes/fetchAllFinancials"));
8283
/* --------------------------------------- */
8384
require("./services/getHoliday");
8485
require("./tasks/dailyStockUpdater");

routes/fetchAllFinancials.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// routes/fetchAllFinancials.js
2+
const express = require("express");
3+
const router = express.Router();
4+
const path = require("path");
5+
const fs = require("fs").promises;
6+
7+
const FinancialSummary = require("../models/FinancialSummary");
8+
const { getFinancialSummary } = require("../services/fetchFinancialData");
9+
const getCorpCodeByStockCode = require("../utils/getCorpCode");
10+
11+
/**
12+
* GET /api/fetch-all?start=2020&end=2024
13+
* kospi200_codes.json에 있는 모든 종목에 대해 재무요약을 가져와 저장합니다.
14+
*/
15+
router.get("/fetch-all", async (req, res) => {
16+
const startYear = parseInt(req.query.start, 10) || 2020;
17+
const endYear = parseInt(req.query.end, 10) || new Date().getFullYear();
18+
19+
try {
20+
// 1) JSON 파일 읽기
21+
const codesPath = path.resolve(__dirname, "../kospi200_codes.json");
22+
const raw = await fs.readFile(codesPath, "utf8");
23+
const kospi200 = JSON.parse(raw); // [ { _id: "005930", name: "삼성전자" }, … ]
24+
25+
const results = [];
26+
27+
// 2) 순회하며 fetch & upsert
28+
for (const { _id: stockCode, name } of kospi200) {
29+
try {
30+
const entries = await getFinancialSummary(
31+
stockCode,
32+
startYear,
33+
endYear
34+
);
35+
if (!entries || entries.length === 0) {
36+
results.push({ stockCode, name, status: "no data" });
37+
continue;
38+
}
39+
40+
const corp_code = getCorpCodeByStockCode(stockCode);
41+
await FinancialSummary.updateOne(
42+
{ stock_code: stockCode },
43+
{ $set: { stock_code: stockCode, corp_code, entries } },
44+
{ upsert: true, runValidators: true, setDefaultsOnInsert: true }
45+
);
46+
47+
results.push({
48+
stockCode,
49+
name,
50+
status: "saved",
51+
count: entries.length,
52+
});
53+
} catch (err) {
54+
results.push({
55+
stockCode,
56+
name,
57+
status: "error",
58+
error: err.message,
59+
});
60+
}
61+
}
62+
63+
// 3) 결과 리턴
64+
res.json({ message: "✅ 완료", results });
65+
} catch (err) {
66+
res.status(500).json({ error: err.message });
67+
}
68+
});
69+
70+
module.exports = router;

services/metricsService.js

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// services/metricsService.js
21
const FinancialSummary = require("../models/FinancialSummary");
32
const { fetchStockPrice } = require("./fetchStockPrice");
43

@@ -28,49 +27,48 @@ async function computeMetrics(stockCode, dateStr) {
2827

2928
// 기준일자 파싱
3029
const baseDate = new Date(dateStr.replace(/\./g, "-"));
31-
// console.log(baseDate);
3230
if (isNaN(baseDate)) throw new Error("잘못된 날짜 형식: " + dateStr);
3331

34-
// entries 에 periodDate 부착 & 기준 이전만 필터링
35-
const withDate = doc.entries.map((e) => ({
36-
...e,
37-
periodDate: getPeriodDate(e),
38-
}));
39-
const valid = withDate
32+
// entries에 periodDate 부착 & 기준 이전만 필터
33+
const valid = doc.entries
34+
.map((e) => ({ ...e, periodDate: getPeriodDate(e) }))
4035
.filter((e) => e.periodDate <= baseDate)
4136
.sort((a, b) => a.periodDate - b.periodDate);
4237

43-
if (valid.length < 2) throw new Error("조회일자 이전에 분기가 부족합니다");
38+
// 최소 4분기 데이터 확보
39+
if (valid.length < 4) {
40+
throw new Error("4분기 이상 데이터가 필요합니다.");
41+
}
4442

45-
// TTM 합산 및 평균자본
43+
// 최신 분기 데이터
44+
const last = valid[valid.length - 1];
45+
46+
// TTM 합산값과 주식수
47+
const profit = last.profit;
48+
const ttmRevenue = last.revenue_ttm;
49+
const equityTTM = last.equity_ttm;
50+
const shareCount = last.istc_totqy;
51+
52+
if (!profit || !ttmRevenue || !shareCount) {
53+
throw new Error("TTM 데이터가 부족하여 EPS/BPS/PSR 계산이 불가능합니다.");
54+
}
55+
56+
// EPS/BPS 보정 계산
57+
const eps = last.eps != null ? last.eps : profit / shareCount;
58+
const bps = last.bps != null ? last.bps : last.equity / shareCount;
59+
60+
// 주가 가져오기
4661
const { price: stockPrice, date: priceDate } = await fetchStockPrice(
4762
stockCode,
4863
dateStr
4964
);
5065

51-
const ttmProfit = valid[valid.length - 1].profit; //당기 순이익
52-
const ttmRevenue = valid[valid.length - 1].revenue_ttm; //매출액
53-
const ttmequity = valid[valid.length - 1].equity_ttm; // 순자산
54-
const shareCount = valid[valid.length - 1].istc_totqy; // 총 발행 주식수
55-
const profit_diff = valid[valid.length - 1].profit_diff; // 증감액
56-
const profit_diff_rate = valid[valid.length - 1].profit_diff_rate; // 증감률
57-
// const revenue = valid[valid.length - 1].revenue;
58-
// const netProfit_govern = valid[valid.length - 1].net_profit_govern; // 순이익
59-
// const profitMargin = valid[valid.length - 1].profit_margin; // 순 이익률
60-
// const growthRate = valid[valid.length - 1].growth_rate; //순 이익 성장률
61-
// const operatingProfit = valid[valid.length - 1].operating_profit; // 영업 이익
62-
// const operatingMargin = valid[valid.length - 1].operating_margin; // 영업 이익률
63-
// const operatingGrowthRate = valid[valid.length - 1].operating_growth_rate; // 영업 이익 성장률
64-
6566
// 지표 계산
66-
const eps = valid[valid.length - 1].eps;
67-
const bps = valid[valid.length - 1].bps;
68-
const roe = valid[valid.length - 1].roe;
69-
70-
// const pbr = bps ? stockPrice / bps : null;
71-
// const per = eps ? stockPrice / eps : null;
72-
// const psr = ttmRevenue ? (stockPrice * shareCount) / ttmRevenue : null;
67+
const per = eps ? stockPrice / eps : null;
68+
const pbr = bps ? stockPrice / bps : null;
69+
const psr = ttmRevenue ? (stockPrice * shareCount) / ttmRevenue : null;
7370

71+
// 시계열 데이터
7472
const series = {
7573
period: valid.map((e) => `${e.bsns_year}.${e.reprt_code}`),
7674
revenue: valid.map((e) => e.revenue),
@@ -84,19 +82,22 @@ async function computeMetrics(stockCode, dateStr) {
8482

8583
return {
8684
price: { price: stockPrice, date: priceDate },
85+
per,
86+
pbr,
87+
psr,
8788
stockPrice,
8889
shareCount,
8990
// per,
9091
// psr,
9192
// pbr,
9293
eps,
9394
bps,
94-
roe,
95-
ttmProfit,
95+
roe: last.roe,
96+
ttmProfit: profit,
9697
ttmRevenue,
97-
ttmequity,
98-
profit_diff,
99-
profit_diff_rate,
98+
equityTTM,
99+
profit_diff: last.profit_diff,
100+
profit_diff_rate: last.profit_diff_rate,
100101
series,
101102
};
102103
}

0 commit comments

Comments
 (0)