Harga live otomatis via cron: Yahoo Finance tiap 6 menit saat jam bursa IDX
- saat.saham: field harga_terakhir, harga_update_at + method _cron_fetch_live_prices() - _in_trading_window() pakai timezone Asia/Jakarta eksplisit (container UTC) 08:45-16:00 WIB - Data ir.cron (6 menit): ambil harga saham aktif, update KALAU berubah, sinkronkan ke saat.portofolio.harga_sekarang utk hitung equity - Terverifikasi: 8 saham ter-fetch & ter-update (07:01 UTC / 14:01 WIB)
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import urllib.request
|
||||
import json
|
||||
|
||||
from datetime import datetime
|
||||
from odoo import api, fields, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SaatSaham(models.Model):
|
||||
"""Master daftar saham / emiten yang dicatat."""
|
||||
@@ -13,6 +20,11 @@ class SaatSaham(models.Model):
|
||||
bursa = fields.Char(string="Bursa", default="IDX")
|
||||
sector = fields.Char(string="Sektor")
|
||||
active = fields.Boolean(string="Aktif", default=True)
|
||||
# Harga pasar terakhir (hasil fetch live), hanya utk jalur cron
|
||||
harga_terakhir = fields.Monetary(string="Harga Pasar Terakhir", currency_field='currency_id')
|
||||
harga_update_at = fields.Datetime(string="Harga Diperbarui")
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', string="Mata Uang", default=lambda self: self.env.ref('base.IDR', raise_if_not_found=False) or self.env.company.currency_id)
|
||||
|
||||
# o2m convenience menampilkan semua transaksi saham ini
|
||||
transaksi_ids = fields.One2many('saat.transaksi', 'saham_id', string="Transaksi")
|
||||
@@ -20,3 +32,57 @@ class SaatSaham(models.Model):
|
||||
_sql_constraints = [
|
||||
('saham_uniq', 'unique(name)', "Kode saham sudah terdaftar."),
|
||||
]
|
||||
|
||||
# ---- Live price fetch (dipakai cron) ----
|
||||
@api.model
|
||||
def _yahoo_price_idr(self, ticker):
|
||||
"""Ambil harga pasar terakhir dari Yahoo Finance utk saham IDX (.JK)."""
|
||||
url = f"https://query1.finance.yahoo.com/v8/finance/chart/{ticker}.JK?range=1d&interval=1d"
|
||||
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64)'})
|
||||
try:
|
||||
d = json.load(urllib.request.urlopen(req, timeout=15))
|
||||
meta = d['chart']['result'][0]['meta']
|
||||
return meta.get('regularMarketPrice')
|
||||
except Exception as e:
|
||||
_logger.warning("Yahoo fetch gagal utk %s: %s", ticker, e)
|
||||
return None
|
||||
|
||||
@api.model
|
||||
def _in_trading_window(self):
|
||||
"""Jam bursa IDX (Asia/Jakarta/WIB): sesi 1 09.00-12.00, sesi 2 13.30-15.50.
|
||||
Dengan toleransi ambil 08.45 s/d 16.00 WIB. Gunakan timezone Asia/Jakarta eksplisit
|
||||
(container biasanya UTC)."""
|
||||
try:
|
||||
from zoneinfo import ZoneInfo
|
||||
now = datetime.now(ZoneInfo('Asia/Jakarta'))
|
||||
except Exception:
|
||||
import pytz
|
||||
now = datetime.now(pytz.timezone('Asia/Jakarta'))
|
||||
hm = now.hour * 60 + now.minute
|
||||
return 8 * 60 + 45 <= hm <= 16 * 60 # 08:45 - 16:00 WIB
|
||||
|
||||
@api.model
|
||||
def _cron_fetch_live_prices(self):
|
||||
"""Cron: ambil harga live semua saham aktif, update bila berubah.
|
||||
Hanya saat jam bursa. Idempoten & aman (tidak sentuh transaksi/saham)."""
|
||||
if not self._in_trading_window():
|
||||
_logger.info("Di luar jam bursa, lewati fetch harga.")
|
||||
return False
|
||||
|
||||
sahams = self.search([('active', '=', True)])
|
||||
now = fields.Datetime.now()
|
||||
updated = 0
|
||||
for s in sahams:
|
||||
price = self._yahoo_price_idr(s.name.strip())
|
||||
if price is None:
|
||||
continue
|
||||
changed = False
|
||||
if abs((price or 0) - (s.harga_terakhir or 0)) > 1e-6:
|
||||
changed = True
|
||||
if changed:
|
||||
s.write({'harga_terakhir': price, 'harga_update_at': now})
|
||||
# sinkronkan ke portofolio terkait utk hitung equity
|
||||
self.env['saat.portofolio'].search([('saham_id', '=', s.id)]).write({'harga_sekarang': price})
|
||||
updated += 1
|
||||
_logger.info("Fetch harga selesai: %d saham di-perbarui (dari %d)", updated, len(sahams))
|
||||
return updated
|
||||
|
||||
Reference in New Issue
Block a user