2012年11月30日 星期五

《The 100 Best Books of All Time》走過的記錄

Link: http://en.wikipedia.org/wiki/The_100_Best_Books_of_All_Time


有印象的。

The Divine Comedy, Dante Alighieri
The Stranger, Albert Camus
Blindness, José Saramago
Hamlet, William Shakespeare
King Lear, William Shakespeare
Othello, William Shakespeare
The Old Man and the Sea, Ernest Hemingway ((這可是規規舉舉的看完英文小說))
Anna Karenina, Leo Tolstoy
A Madman's Diary, Lu Xun


忘記了,有點可悲的腦子。

Pride and Prejudice, Jane Austen ((規規舉舉看完英文小說,不過印象極低,慘))


我是文化沙漠漠長

《財報分析》3030


PDF: https://docs.google.com/open?id=0BzU93gXbRyB2RkplcHE5ZHRtbzA


除了三個月平均營收趨勢向下,其他都不錯。但就是平均營收趨勢向下,沒有理由買進持有,有點可惜,因為外部股東報酬率 > 15%,很肥美。

[PostgreSQL] temp table 搭配 for loop


終於用很髒的方式兜出來了。

CREATE TEMPORARY TABLE ExpectedRoe
(
    stock_code text NOT NULL,
    expected_roe double precision
);

DO $$DECLARE r record;
BEGIN
    FOR r IN SELECT distinct(stock_code) as stock_code FROM BalanceSheet
    LOOP
        insert into ExpectedRoe(stock_code, expected_roe)
        select
            r.stock_code, expected_roe
        from ...; -- 用 r.stock_code 當參數
    END LOOP;
END$$;

SELECT * from ExpectedRoe where expected_roe >= 0.1 
order by expected_roe desc;
某個例子:
DROP TABLE IF EXISTS AllRoe;
CREATE TEMPORARY TABLE AllRoe
(
    stock_code text NOT NULL,
    roe double precision
);

DO $$DECLARE r record;
BEGIN
    FOR r IN SELECT distinct(stock_code) as stock_code FROM BalanceSheet
    LOOP
        insert into AllRoe(stock_code, roe)
        select
            r.stock_code, T.roe
        from 
        (
            with IndividualRoe as 
            (
                select activity_date, roe from roe(r.stock_code)
            )
            select roe from IndividualRoe
            where activity_date in
            (
                select max(activity_date) from IndividualRoe
            )
        ) as T;
    END LOOP;
END$$;

select * from AllRoe;

媒體巨獸《二》


http://news.chinatimes.com/focus/11050105/112012113000092.html


不過有些東西不用尺就知道是禮義廉來的。

媒體巨獸《一》


媒體巨獸大勢難擋,如何判斷是非,得要有自己的一把尺,自己的思考。事事懷疑,不管眾人說什麼,有把尺,還怕是非難斷?

《熱門族群》大立光明年獲利可望年增50%,帶動玉晶光大漲
2012/11/30 12:51 時報資訊 

【時報記者張漢綺台北報導】光學鏡頭廠-大立光 (3008) 業績可望一路旺到明年第1季,獲外資法人及投信一致看好,外資法人普遍預估,大立光明年獲利可望較今年成長50%,每股盈餘自50元起跳,在法人買盤力挺下,大立光今天股價再度強攻,盤中一度大漲近4%,逼近800元關卡,再創2011年9月22日以來新高價,也帶動玉晶光 (3406) 股價同步走高。

智慧型手機愈來愈強調照相功能,且手機薄型化,尤其是蘋果iPhone 5後鏡頭薄型化、前鏡頭改為HD,使得平均售價均提升,亦讓大立光在蘋果單隻手機鏡頭產值增加60%,法人預估,大立光供應給蘋果iPhone 5的後鏡頭供貨比率將自4S的50%增加至70%,前鏡頭比率則提升至40%,預估蘋果對大立光明年營收產值可望達1倍;除蘋果iPhone 5手機外,宏達電 (2498) HTC、Nokia、RIM在今年第4季到明年第1季力推多款新機,尤其是明年第1季旗鑑機陸續上市,而大陸智慧型手機的相機鏡頭亦自5M轉至8M,受惠高階鏡頭訂單集中,且新產能自10月底至明年第1季陸續開出,讓大立光成為最大受惠者,業績可望一路旺到明年。

大立光10月合併營收為24.16億元,較9月營收成長34%、較去年同月成長72%,創下單月歷史新高,累計前10月合併營收139.04億元,較去年同期成長4%;由於11月合併營收可望再創新高,以10月及11月合併營收來預估,外資普遍認為,大立光第4季營收可望較第3季成長約50%,獲利在經濟規模及良率提升下,可望季增達70%,全年每股盈餘可望上看35元,明年獲利可望較今年成長50%,每股盈餘自50元起跳。

這種興奮的活水,看多不免麻木,但事情總要有的是非,不能因為米果說什麼,我們就反什麼,說話講證據,心理總是踏實一點。

ROE、純益率、總資產週轉率向下,權益乘數向上,經營可能陷入困境。財務結構很好。流動比率及速動比率下滑,但絕對值不錯。長期從事本業,很好。三大獲利能力下滑。

外部股東實質報酬率 = 要求的預期報酬率 = 4.32%。我最低要求 10%,理想是 15%。

SQL query:

select 
    Y.roe / X.pbr as expected_roe
from
(
    select 
        pbr 
    from 
        ListedCoStatistics 
    where 
        stock_code = '3008' 
        and report_date in 
        (
            select max(report_date) from ListedCoStatistics where stock_code = '3008'
        )
) as X,
(
    with W as
    (
        with T as
        (
            select
                A.activity_date,
                A.report_date,
                A.number as shareholder_equity,
                B.number as net_income
            from
                BalanceSheet as A,
                IncomeStmt as B
            where
                A.stock_code = B.stock_code
                and A.activity_date = B.activity_date
                and A.report_date = B.report_date
                and A.item = '股東權益總計'
                and B.item = '合併總損益'
                and A.report_type = 'C'
                and B.report_type = 'C'
                and A.stock_code = '3008'
                and A.number != 0
                and B.number != 0
        )
        select 
            V.activity_date, 
            V.annual_adjusted_net_income/V.shareholder_equity as roe
        from
        (
            select
                T.activity_date,
                T.shareholder_equity,
                case
                    when date_part('month', T.activity_date) = 3 then T.net_income * 4/1
                    when date_part('month', T.activity_date) = 6 then T.net_income * 4/2
                    when date_part('month', T.activity_date) = 9 then T.net_income * 4/3
                    else T.net_income
                end as annual_adjusted_net_income
            from 
                T, 
                (
                    select activity_date, max(report_date) as report_date from T
                    group by activity_date
                ) as U
            where 
                T.activity_date = U.activity_date
                and T.report_date = U.report_date
            order by T.activity_date
        ) as V
    )
    select roe from W where activity_date in (select max(activity_date) from W)
) as Y

斷行懶得處理。

2012年11月29日 星期四

微腦殘政府



相對於米果中時,突然覺得聯合很可愛。


最礙眼的一句話:「衛生署樂觀估計,二代健保可維持財務至二○一六總統大選年。」鬼島不會維持制度,只會苟延殘喘,過了這一關就好了。打帶跑,我不是說衛生署腦殘,我是說政府全都是腦殘 ((除了彭淮南))。


二代健保費率4.91% 40100薪以下保費增
作者: 記者楊湘鈞、黃文彥╱台北報導 | 聯合新聞網 – 2012年11月30日 上午2:38

二代健保費率底定,行政院長陳冲拍板調降一般保費費率,由百分之五點一七降為百分之四點九一。政院表示,明年元旦新制上路後,百分之八十七的民眾負擔不增或減少,另有百分之十三的民眾(約三百萬人)因補充保費增加,整體保費負擔增加。

衛生署樂觀估計,二代健保可維持財務至二○一六總統大選年

二代健保明年一月一日上路,保費除了現行由薪資計算的「一般保費」之外,將再加上「補充保費」,計費項目包括高於四個月的獎金、兼職所得、租金收入、執行業務收入、股利所得及利息等六項

雖然衛生署估計,調降一般保險費率至百分之四點九一後,百分之八十七的民眾負擔不增或減少,但隨著二代健保上路,政府將一併取消現行對收入較低民眾的「健保費率調漲差額專案補貼」,月薪四萬零一百元以下收入的民眾,繳交的一般保費將提高,每人每月保費將增加廿一元至四十四元不等。

行政院發言人鄭麗文說,二代健保已建立收支連動機制,未來將由監理、費協整併後的健保會,整體考量保險收入與醫療給付;如預期發生財務缺口,可運用收支連動機制,由健保會評估後提出最適當的費率,報衛生署轉行政院核定,以確保財務平衡。

健保局前總經理朱澤民表示,補助財源最終還是來自納稅人的錢,健保局應該專心進行健保改革,讓救濟弱勢族群回歸社會救助制度。

民間監督健保聯盟發言人滕西華表示,健保支出每年以百分之五的速度成長,收入成長率卻僅百分之一點七,加上景氣變動,很難永續經營

2012年11月28日 星期三

《Stocktotal》之五:抓取個股股利政策

抓取資料三大步驟:
  1. Source web contents
  2. Parse web contents
  3. Insert database


1. Source Web Contents

Link: http://estockweb.standardchartered.com.tw/z/zc/zcc/zcc_1101.djhtm

我們稍微把 1101 改成 2498,就變成 2498的股利政策,表示我們可以透過這些網路資料來擷取個股股利政策。

擷取方式採用 third-party 軟體:wget。wget 有 Windows 版本及 Mac 版本,移植性不用擔心。那麼該怎麼用 Python 寫出跨平台的程式呢?這個網路上有很多資源,我也是抄別人的做法加以改良。

首先,先寫 interface,wget.py:
import platform

os = platform.system()
if os == 'Windows':
    from . import wget_win as wget_private
elif os == 'Darwin':
    from . import wget_mac as wget_private
else:
    raise Exception('Please add support for your platform')

def wget(cmdline):
    wget_private.wget(cmdline)
就是一個 wget method,舉例來說:
url --waitretry=3 -O dest_file
抓哪個網址,waitretry 設為 3 ((這是很重要的設定,畢竟網路環境不穩,無法無限制的等待,也不能不等待,我們得選中庸之道)),存在哪個目的檔。

程式中,我們利用 platform module 得到目前跑什麼平台。例如是 Windows 平台,那我就寫 wget_win.py,有點 d-pointer 的味道。
import os

def wget(cmdline):
    wget = os.path.abspath('./core/thirdparty/wget/wget.exe')
    assert os.path.isfile(wget)  
    wget_cmdline = '''{wget} {cmdline}'''.format(wget=wget, cmdline=cmdline)
    os.system(wget_cmdline)

wget_mac.py
import os

def wget(cmdline):
    wget_cmdline = '''wget {cmdline}'''.format(cmdline=cmdline)
    os.system(wget_cmdline)

就是有那麼微微的不一樣。



2. Parse Web Contents

這是最複雜的部分,每個 web contents 可能差不多,也可能差很多。因為 web contents 多是 HTML 格式,關於取資料,lxml module 功能算十分強大,當然還有更強大的。

但中文網頁實在是有夠混亂的,特別是 encoding,我們得多試幾種 decoder。舉例來說,讀取 html content 的程式片斷如下
src_fd = open(src_file, 'rb')
src_content = src_fd.read()
src_fd.close()

content = None
try:
    content = html.fromstring(src_content.decode('big5-hkscs'))
except UnicodeDecodeError as e:
    self.LOGGER.debug(e)
    content = html.fromstring(src_content.decode('gb18030'))
如果還沒辦法 decode,那可就麻煩了,目前網頁這兩個 decoders 就夠用了。

如果熟悉 xpath 的用法,接下來取資料就很容易了。通常這些網頁是用程式生出來的,格式自然比較規矩。xpath 程式片斷
for table in content.xpath('//html/body/div/table/tr/td[@width="99%"]/table/tr/td/table/tr/td/table'):
    for yearly_dataset in table.xpath('./tr'):
        yearly_data = yearly_dataset.xpath('./td/text()')
        if len(yearly_data) is 7:
            activity_date = self.get_date(yearly_data[0])
            if not activity_date:
                continue
            record = [
                self.STOCK_CODE, 
                activity_date, 
                self.get_double(yearly_data[1]),
                self.get_double(yearly_data[2]),
                self.get_double(yearly_data[3]),
                self.get_double(yearly_data[4]),
                self.get_double(yearly_data[5]),
                self.get_double(yearly_data[6]),
            ]
總之知道基本技巧後,其它就是自由發揮,不會就 google 唄。



3. Insert Database

以 PostgreSQL 為例,我們得先準備好 schema
create table if not exists StockDividend
(
    creation_dt timestamp default current_timestamp,
    stock_code text not null,
    activity_date date not null, 
    cash_dividend double precision,
    stock_dividend_from_retained_earnings double precision,
    stock_dividend_from_capital_reserve double precision,
    stock_dividend double precision,
    total_dividend double precision,
    profit_sharing_percentage double precision,
    unique (stock_code, activity_date)
);

用 pgAdmin III 執行 SQL statement,並且留意執行身分,記得把權限開給適當的人。接著就是用 py-postgresql module 執行 PostgreSQL 操作。


這邊又有點小東西可以講。

Program to an interface, not an implementation


直接操作 PostgreSQL 或許方便,但將來想改用 MySQL 或是 SQL Server,得考慮程式好不好改,因為當初我也從 SQLite 改到 PostgreSQL。

db_config.py
DB_TYPE = 'postgres'
如果將來想換 database,直接在這邊設定,加入適當的 implementation 就可以了。


insertion/insertion_factory.py
from .. import db_config
db_type = db_config.DB_TYPE

if db_type == 'sqlite':
    from .sqlite import insertion_factory as factory_private
elif db_type == 'postgres':
    from .postgres import insertion_factory as factory_private
    
class InsertionFactory():
    @staticmethod
    def insertion():
        return factory_private.InsertionFactory().insertion()
對於 insert,我們利用 factory pattern,根據 DB_TYPE,生成適當的 Insertion object。這裡我比較龜毛,最外面的 factory 只會生出 PostgreSQL 專屬的 factory,最後怎麼生出來,由專屬的 factory 來煩惱。

insertion/postgresql/insertion_factory.py
from . import insertion
    
class InsertionFactory():
    @staticmethod
    def insertion():
        return insertion.Insertion()

insertion/postgresql/insertion.py
import postgresql

class Insertion():

    def __init__(self):
        self.CONN_STRING = 'pq://stocktotal:stocktotal@localhost:5432/stocktotal'
        self.DB_CONN = None
        
    def open(self):
        self.DB_CONN = postgresql.open(self.CONN_STRING)
        
    def close(self):
        self.DB_CONN.close()
        self.DB_CONN = None

    def insert(self, sql_cmd, record):
        try:
            fixed_record = [None if _.strip() == '' else _ for _ in record]
            prepared_stmt = self.DB_CONN.prepare(sql_cmd)
            prepared_stmt(*fixed_record)
        except postgresql.exceptions.UniqueError:
            pass

    def insert_stock_dividend(self, record):
        sql_cmd = \
            '''
            insert into StockDividend(
                stock_code,
                activity_date,
                cash_dividend,
                stock_dividend_from_retained_earnings,
                stock_dividend_from_capital_reserve,
                stock_dividend,
                total_dividend,
                profit_sharing_percentage
            ) values(
                $1, 
                $2::text::date, 
                $3::text::float8, 
                $4::text::float8,
                $5::text::float8,
                $6::text::float8,
                $7::text::float8,
                $8::text::float8
            )
            '''
        self.insert(sql_cmd, record)
外面 client user 只需傳 list 進來就可以了。當然這樣的 interface 有點死板,第一個是股號,第二個是日期,第三個是等等之類的,不過好用就好了,將來要改,也不會改的太疲憊。



附錄:

standardchartered_source.py
import csv
import logging
import os
from datetime import date

class StandardcharteredSource():

    def __init__(self):
        from ..db.insertion import insertion_factory
        
        self.LOGGER = logging.getLogger()        
        self.URL_TEMPLATE = ''
        self.STOCK_CODE = None
        self.SOURCE_TYPE = None
        self.HTML_DIR = ''
        self.CSV_DIR = ''
        self.DB_INSERTION = insertion_factory.InsertionFactory().insertion()
        
    def source(self, stock_code):
        self.STOCK_CODE = stock_code
        self.source_url_to_html(self.HTML_DIR)        
        self.source_html_to_csv(self.HTML_DIR, self.CSV_DIR)
        self.source_csv_to_db(self.SOURCE_TYPE, self.CSV_DIR, self.DB_INSERTION)

    def source_url_to_html(self, dest_dir):
        if not os.path.exists(dest_dir):
            os.makedirs(dest_dir)
        url = self.__get_url()
        dest_file = self.get_filename(dest_dir, 'html')
        self.__wget(url, dest_file)
        
    def source_html_to_csv(self, src_dir, dest_dir):
        pass

    def source_csv_to_db(self, source_type, src_dir, db_insertion):            
        src_file = self.get_filename(src_dir, 'csv')
        if not os.path.isfile(src_file):
            return    
        self.LOGGER.debug('''{src_file} => db'''.format(src_file=src_file))
        fd = open(src_file, 'r')
        csv_reader = csv.reader(fd)
                
        INSERT_SOURCE_TYPE_MAP = {
            'capital_structure_summary': db_insertion.insert_capital_structure_summary,
            'stock_dividend': db_insertion.insert_stock_dividend,
        }                 
                
        db_insertion.open()
        for r in csv_reader:
            INSERT_SOURCE_TYPE_MAP[source_type](r)
            self.LOGGER.debug(r)
        db_insertion.close()
        
        fd.close()

    def get_filename(self, src_dir, ext):
        return os.path.join(src_dir, self.STOCK_CODE + '.' + ext) 

    # Get date from ROC year to date (Python data type)
    def get_date(self, literal):
        try:
            return date(int(literal) + 1911, 1, 1)
        except ValueError:
            return None        

    def get_double(self, literal):
        literal = literal.replace(',','')    
        try:
            return float(literal)
        except ValueError:
            return None
            
    def __get_url(self):
        return self.URL_TEMPLATE % self.STOCK_CODE

    def __wget(self, url, dest_file):
        from ..base import wget
        cmdline = '''\"{url}\" --waitretry=3 -O \"{dest_file}\"'''.format(url=url, dest_file=dest_file)
        wget.wget(cmdline)

stock_dividend_source.py
import csv
import os
from lxml import html

from . import standardchartered_source

class StockDividendSource(standardchartered_source.StandardcharteredSource):

    def __init__(self):
        standardchartered_source.StandardcharteredSource.__init__(self)

        self.URL_TEMPLATE = '''http://estockweb.standardchartered.com.tw/z/zc/zcc/zcc_%s.djhtm'''
        self.SOURCE_TYPE = 'stock_dividend'
        self.HTML_DIR = '../dataset/stock_dividend/html/'
        self.CSV_DIR = '../dataset/stock_dividend/csv/'

    def source_html_to_csv(self, src_dir, dest_dir):
        assert os.path.isdir(src_dir)
        if not os.path.exists(dest_dir):
            os.makedirs(dest_dir)

        src_file = self.get_filename(src_dir, 'html')
        dest_file = self.get_filename(dest_dir, 'csv')
        self.LOGGER.debug('''{src_file} => {dest_file}'''.format(src_file=src_file, dest_file=dest_file))
        assert os.path.isfile(src_file)
        
        dest_fd = open(dest_file, 'w', newline='')
        csv_writer = csv.writer(dest_fd)
        
        src_fd = open(src_file, 'rb')
        src_content = src_fd.read()
        src_fd.close()

        content = None
        try:
            content = html.fromstring(src_content.decode('big5-hkscs').replace('&nbsp;', ' ').replace('<BR>', ''))
        except UnicodeDecodeError as e:
            self.LOGGER.debug(e)
            content = html.fromstring(src_content.decode('gb18030').replace('&nbsp;', ' ').replace('<BR>', ''))

        for table in content.xpath('//html/body/div/table/tr/td[@width="99%"]/table/tr/td/table/tr/td/table'):
            for yearly_dataset in table.xpath('./tr'):
                yearly_data = yearly_dataset.xpath('./td/text()')
                if len(yearly_data) is 7:
                    activity_date = self.get_date(yearly_data[0])
                    if not activity_date:
                        continue
                    record = [
                        self.STOCK_CODE, 
                        activity_date, 
                        self.get_double(yearly_data[1]),
                        self.get_double(yearly_data[2]),
                        self.get_double(yearly_data[3]),
                        self.get_double(yearly_data[4]),
                        self.get_double(yearly_data[5]),
                        self.get_double(yearly_data[6]),
                    ]
                    csv_writer.writerow(record)
        dest_fd.close()

[PostgreSQL] Changing a Column's Data Type


To convert a column to a different data type, use a command like this:
ALTER TABLE products ALTER COLUMN price TYPE numeric(10,2);
This will succeed only if each existing entry in the column can be converted to the new type by an implicit cast. If a more complex conversion is needed, you can add a USING clause that specifies how to compute the new values from the old.

PostgreSQL will attempt to convert the column's default value (if any) to the new type, as well as any constraints that involve the column. But these conversions may fail, or may produce surprising results. It's often best to drop any constraints on the column before altering its type, and then add back suitably modified constraints afterwards.


Reference: http://www.postgresql.org/docs/8.1/static/ddl-alter.html


原由:企圖把所有 float4 (real) data type 全部改成 float8 (double precision)。如果 Python 使用 py-postgresql insert 資料,prepared statement
insert into OperatingIncome(
    report_date, 
    stock_code,
    activity_date,
    income
) values($1::text::date, $2, $3::text::date, $4::text::real)
要改成
insert into OperatingIncome(
    report_date, 
    stock_code,
    activity_date,
    income
) values($1::text::date, $2, $3::text::date, $4::text::float8)

留意一下。


Anna Karenina (2012)

非常喜歡
劇本、轉場、音樂、特效相互支援
使得這大部頭鉅作一點都不悶

沒什麼好評論的
有興趣的 看劇前或後 請補課吧!

□ 舊版本
Anna Karenina (1997)

□ 有婦之夫/禁忌之愛/外遇/死亡
《危險關係》
Dangerous Liaisons (1988)
Cruel Intentions (1999) 
The Duchess (2008) 

□ 堅持/等待/求婚橋段/好男無敵
《理性與感情》
《傲慢與偏見》
Sense and Sensibility (1995)
Pride and Prejudice (1995) BBC
Pride & Prejudice (2005)

2012年11月27日 星期二

《財報分析》2498


TODO:補齊股利、股本形成等資料。


Link: https://docs.google.com/open?id=0BzU93gXbRyB2Y3hBM2RUYUgxWWs

(要抓回來才看得清楚,Google想自己做 Adobe Reader?還有許多可以努力喔)

2012年11月26日 星期一

《Stocktotal》之四:報表成果發表


放在 Google:https://docs.google.com/open?id=0BzU93gXbRyB2WFJkd0V3T0NobUE


微政府帶頭作帳


舉雙手贊成。

當股價波動幅度變大,所謂的買點才會一一浮現。




救台股 現貨「先買後賣」當沖擬開放


【聯合報╱記者薛翔之/台北報導】
2012.11.27 05:24 am

振興股市! 現股可望開放「先買後賣」 / 陳雲上

為提振股市交易,金管會昨天宣布,將研擬開放現股「先買後賣」當日沖銷交易,列為金管會「振興股市方案」重要項目之一,預計最快年底上路。

立法院財政委員會昨晚宴請金管會主委陳裕璋、中央銀行總裁彭淮南,以及財政部長張盛和財金「三巨頭」,證交所董事長薛琦等證券周邊單位以及八大公股銀行董事長、總經理也全數出席。

陳裕璋指出,將積極配合行政院指示,提出振興股市方案。張盛和強調,不會緩徵證所稅,但只要美國「財政懸崖」解決,一個月後台股低迷的情況就可解決;近日政府點火成功,帶動台股連二天合計大漲三百點。

行政院長陳冲上周指示政務委員管中閔端出振興股市方案。金管會副主委吳當傑昨天說,上周五已邀集證券交易所和證券商公會討論。

吳當傑說,券商和交易所提出五項方案,其中之一建議開放現貨先買後賣的當沖交易,由於可放大股市成交量,金管會正在研議放寬的可行性。

不過,他說,由於投資人買入現股後,如果收盤前賣不掉,將增加違約機率,因此已要求證交所研究配套措施,讓券商強化風險管理,審慎評估投資人的信用,以降低違約機率。

另外,證券自營商希望比照保險業的作法,放寬股票評價(計算持有股票的價格)方式。金管會官員解釋,保險公司的股票,原本是根據每年六月或十二月底的收盤價評價,現在已改為可採半年均價計價,如此當股市不好時,認列的股票投資虧損金額可降低

另外,券商也表示,證券商發行權證避險交易的證交稅率,財政部雖已同意由千分之三降到千分之一,但希望能盡速修法實施,並多舉辦上市櫃公司的業績說明會。

【2012/11/27 聯合報】

《Stocktotal》之三:JasperReports export to PDF


Export to PDF in English

絕對沒有問題。Trivial.



Export to PDF in Traditional Chinese

問題就來了,而且這是多數人的問題,喵的,對中文超不友善的。網路上 google 資料多不合用,還好我找到這篇 blog: 用 Java 產出中文的 JasperReports PDF



使用 iReport export to PDF

這大多找的到答案。在 Win7 環境,記得使用 Administrator 身份執行 iReport 4.8.0,這樣才能順利安裝中文字型。



使用 JasperReports API export to PDF

將標楷體 export as extension as jar file,然後加到 classpath 就可以了。當然,我們還要把 iText-2.1.7.js1.jar 加進來,這個在 JasperReports Library 可以找的到。


微詐騙集團



騙子才是最好賺的行業。敝國總統張麻子就是最好的範例。


投資油畫保證獲利 半年吸金5億
2012/11/26 17:25

檢調破獲油畫吸金集團,上千位受害人被騙走5億元,嚴姓主嫌兩年前就因吸金被捕,卻不知悔改,重出江湖,還自稱美國上市公司主席,旗下有好幾百位簽約畫家,計畫進軍中國,他向被害人保證,先買畫5年後價值翻倍,甚至連複製畫都能投資,連藝術投資專家也覺得好扯,藝術投資絕不可能保證獲利。

穿襯衫繫領帶打扮得人模人樣,口中還說愛台灣,這位自稱David的男子,其實是吸金集團主嫌嚴程德,2年前他就因為吸金被抓,沒想到重出江湖越騙越大,檢調追查,嚴程德集團以培養新銳畫家為由,說服被害人投資油畫,分兩種專案,一種買真跡畫,先花9萬3,聲稱5年後公司以兩倍價錢買回,另一種是複製畫專案,出10萬元但不帶回畫作,由公司製作複製畫賣到中國,每個月領本利7800元,18期可以賺4萬4。

嚴程德吹牛不怕人來戳,演講時還不斷強調將以美商背景進軍中國,但這種投資畫作保證獲利的方案,聽在藝術投資專家耳裡,簡直是天方夜譚。

畫廊提醒,畫作是否值得投資,可以從得獎紀錄、參展評價、收藏價碼這些公開資訊得知,檢調發現,吸金集團甚至只找沒沒無聞的新畫家,或是非油畫科班學生,買畫當吸金工具,卻照樣騙倒上千人捲走5億元。

《Stocktotal》之二:使用 Apache Ant build tool

Apache Ant is a software tool for automating software build processes. It is similar to Make but is implemented using the Java language, requires the Java platform, and is best suited to building Java projects.


這裡我說明怎麼從 Eclipse build process 移植到 Apache Ant build process。


StocktotalReport 目錄結構:
build.xml
config/ReportGenerator.config
config/StocktotalReport.jrxml
lib/commons-beanutils-1.8.0.jar
lib/commons-collections-2.1.1.jar
lib/commons-configuration-1.9.jar
lib/commons-digester-2.1.jar
lib/commons-logging-1.1.1.jar
lib/groovy-all-2.0.1.jar
lib/jasperreports-4.8.0.jar
lib/jcommon-1.0.15.jar
lib/jfreechart-1.0.12.jar
lib/postgresql-9.2-1002.jdbc3.jar
lib/commons-lang-2.6.jar
lib/commons-cli-1.2.jar
lib/log4j-1.2.17.jar
src/stocktotal/report/Argparser.java
src/stocktotal/report/Program.java
src/stocktotal/report/ReportGenerator.java
src/log4j.xml


Build processes 大致包括幾個流程:
  1. Clean building environment
  2. Compile Java source code to Java classes
  3. Pack classes to a single jar file
第一步沒什麼問題。

第二步最常漏掉的就是把 log4j.xml 一起複製到 classes directory

第三步打包,把 classes directory ((當然包括 log4j.xml)) 全部包成一個 jar 檔,放在 destination directory,這樣還不夠,還要把 lib 目錄下所有的 library 全部 copy 到 destination directory,再把 config 目錄下所有的設定 copy 到 destination directory,將來才好執行


打包 jar 檔要額外設定兩件事:
  • Main Class: stocktotal.report.Program ((程式從哪個 class 的 public static main(String[]) 開始執行))
  • Class Path: lib 底下所有的 library。
這步驟不能忘記。


把以上想法寫成 build process,就是下面的流程。Apache Ant 利用 depends 寫明 build process,有倒敘法的味道。
<?xml version="1.0" encoding="UTF-8"?>
    
<project name="StocktotalReport" basedir="." default="jar">

    <property name="src.dir" value="src" />
    <property name="lib.dir" value="lib" />
    <property name="lib.jars" value="commons-beanutils-1.8.0.jar commons-cli-1.2.jar commons-collections-2.1.1.jar commons-configuration-1.9.jar commons-digester-2.1.jar commons-lang-2.6.jar commons-logging-1.1.1.jar groovy-all-2.0.1.jar jasperreports-4.8.0.jar jcommon-1.0.15.jar jfreechart-1.0.12.jar log4j-1.2.17.jar postgresql-9.2-1002.jdbc3.jar" />
    <property name="config.dir" value="config"/>
    <property name="build.dir" value="build" />
    <property name="classes.dir" value="${build.dir}/classes" />
    <property name="dest.dir" value="${build.dir}/release" />
    <property name="dest.jar" value="${dest.dir}/stocktotal-report.jar" />

    <path id="classpath">
        <fileset dir="${lib.dir}" >
            <include name="**/*.jar" /> 
        </fileset>
    </path>

    <target name="clean">
        <delete dir="${build.dir}" />
    </target>

    <target name="compile" depends="clean">
        <mkdir dir="${classes.dir}" />

        <javac srcdir="${src.dir}" encoding="UTF-8" destdir="${classes.dir}" includeantruntime="false" debug="true">
            <classpath refid="classpath" />
            <include name="**/*.java" />
        </javac>
        
        <!-- Copy all config files to ${classes.dir} -->
        <copy todir="${classes.dir}">
            <fileset dir="${src.dir}">
                <exclude name="**/*.java" />
            </fileset>
        </copy>
    </target>
    
    <target name="jar" depends="compile">
        <jar destfile="${dest.jar}">
            <fileset dir="${classes.dir}" />
            <manifest>
                <attribute name="Main-Class" value="stocktotal.report.Program"/>
                <attribute name="Class-Path" value="${lib.jars}"/>
            </manifest>
        </jar>
        <copy todir="${dest.dir}">
            <fileset dir="${lib.dir}" />
        </copy>
           <copy todir="${dest.dir}">
            <fileset dir="${config.dir}" />
        </copy>        
    </target>

    <target name="run">
        <java jar="${dest.jar}" fork="true" failonerror="true" />
    </target>

</project>

準備妥當後,只要在 command prompt 下 build 就可以了:ant -f build.xml

執行程式,java -jar stocktotal-report.jar -stockcode 2330 -configfile ReportGenerator.config


剛開始使用 Apache Ant 一定很不習慣,但久了就熟悉了。不管電腦環境怎麼改變,command line 才是良好的自動化界面,這不禁讓我想到實假登錄網站,那就不是好的自動化界面。

《Stocktotal》之一:使用 JasperReport 產生報表

這是很大的題目,把我腦細胞搞死好多個。



1. JasperReport

Report Life Cycle:
JasperReports is an open source Java reporting tool that can write to a variety of targets, such as: screen, a printer, into PDF, HTML,Microsoft Excel, RTF, ODT, Comma-separated values or XML files.

生成 JasperReport 報表,必須完整走過三階段:design phase, execution phase, export phase。



1.1 Design Phase

為人熟知的設計工具是 iReport,目前版本是 4.8.0。iReport 當然不是隨意拉拉版面就能了事的,在 execution phase 我們看到 Data,也就是說,除了版面,我們更在意內容。


Data Source,報表的命。

JasperReports 支援很多 data source,因為事前有研究,JasperReports 對 PostgreSQL 的支援算不錯,原先我使用 SQLite 當 database,後來種種因素,採用品質更優良的 PostgreSQL 當 database ((這麼說或許不公平))。


Datasets,報表的資料集合。

有了 SQL database 當 data source,接著要下 SQL query 才能取出 database 的資料。一個 SQL query,代表一個 dataset
SQL query <=> dataset

舉例來說 ROE Dataset 裡的 query 就是:
    <subDataset name="ROE Dataset" uuid="4233d94c-76a5-4422-97c8-3c347be0be3b">
        <parameter name="STOCK_CODE" class="java.lang.String" isForPrompting="false">
            <defaultValueExpression><![CDATA["1301"]]></defaultValueExpression>
        </parameter>
        <queryString>
            <![CDATA[with T as
(
    select
        A.activity_date,
        A.report_date,
        A.number as shareholder_equity,
        B.number as net_income,
        C.number as operating_income,
        D.number as total_assets
    from
        BalanceSheet as A,
        IncomeStmt as B,
        IncomeStmt as C,
        BalanceSheet as D
    where
        A.stock_code = B.stock_code
        and B.stock_code = C.stock_code
        and C.stock_code = D.stock_code
        and A.activity_date = B.activity_date
        and B.activity_date = C.activity_date
        and C.activity_date = D.activity_date
        and A.report_date = B.report_date
        and B.report_date = C.report_date
        and C.report_date = D.report_date
        and A.item = '股東權益總計'
        and B.item = '合併總損益'
        and C.item = '營業收入合計'
        and D.item = '資產總計'
        and A.report_type = 'C'
        and B.report_type = 'C'
        and C.report_type = 'C'
        and D.report_type = 'C'
        and A.stock_code = $P{STOCK_CODE}
        and A.number != 0
        and B.number != 0
        and C.number != 0
        and D.number != 0
)
select
    V.activity_date,
    V.annual_adjusted_net_income/V.shareholder_equity as roe,
    V.annual_adjusted_net_income/V.annual_adjusted_operating_income as
net_profit_margin,
    V.annual_adjusted_operating_income/V.total_assets as total_assets_turnover,
    V.total_assets/V.shareholder_equity as equity_multiplier
from
(
    select
        T.activity_date,
        T.shareholder_equity,
        case
            when date_part('month', T.activity_date) = 3 then T.net_income * 4/1
            when date_part('month', T.activity_date) = 6 then T.net_income * 4/2
            when date_part('month', T.activity_date) = 9 then T.net_income * 4/3
            else T.net_income
        end as annual_adjusted_net_income,
        case
            when date_part('month', T.activity_date) = 3 then
T.operating_income * 4/1
            when date_part('month', T.activity_date) = 6 then
T.operating_income * 4/2
            when date_part('month', T.activity_date) = 9 then
T.operating_income * 4/3
            else T.operating_income
        end as annual_adjusted_operating_income,
        T.total_assets
    from
        T,
        (
            select activity_date, max(report_date) as report_date from T
            group by activity_date
        ) as U
    where
        T.activity_date = U.activity_date
        and T.report_date = U.report_date
    order by T.activity_date
) as V]]>
        </queryString>
        <field name="activity_date" class="java.sql.Date"/>
        <field name="roe" class="java.lang.Double"/>
        <field name="net_profit_margin" class="java.lang.Double"/>
        <field name="total_assets_turnover" class="java.lang.Double"/>
        <field name="equity_multiplier" class="java.lang.Float"/>
    </subDataset>
裡面有一個不尋常的東西,$P{STOCK_CODE},這是 dataset 自身的 parameters,與外面 main  report 的 parameters 沒關連,這點特別注意,否則會踩到很多地雷。

若使用 iReport 生出 dataset,得注意 SQL query 不能使用 parameters,我們得先生出 dataset,再生出 $P{STOCK_CODE} parameter,接著再 edit query,把寫死的 stock code 改成 $P{STOCK_CODE}。

Parameters 有兩個重要 properties 可以設定,一個是 isForPrompting,另一個是 defaultValueExpression。設計之初,我把 Use as a prompting 取消,Default Value Expression 設成 "1301"


Chart/Table,報表的視覺元素。

這是我看重的部分。根據 dataset 的特性,我們選用最合適的 chart。舉例來說,ROE Dataset 是一串 time series 的資料,我們可以用 timeseries chart 來表現 dataset。

如果想在同一 timeseries chart 畫出兩組以上的資料,Y-axis 不免顯得壅擠,我們可以使用 multi axis chart 當 container,然後加入兩個以上的 timeseries chart,然後再把 Y-axis 分邊 ((Axis properties => Position => Right or Bottom))。

如果不想讓報表分頁,可以在報表 properties => Ignore pagination 打勾。當然還有其他小細節,例如在 Excel,我們可以將滿足某些條件的數字用紅色加以強調,那我們就要在 Styles 自己加 style:
    <style name="red current_ratio">
        <conditionalStyle>
            <conditionExpression><![CDATA[$F{current_ratio} < 2]]></conditionExpression>
            <style forecolor="#FF0000"/>
        </conditionalStyle>
    </style>
有時候動手直接改 XML 反而比較快,但要小心改爛的風險。改之前最好備份一下設計檔 ((JRXML,以 XML 呈現報表排版及內容))


當然,我們也能直接使用 Java JasperReports API 設計報表。這是很有用的功能,例如說我想動態改變 dataset 中 parameter 的值。最差的辦法就是直接開 JRXML,用 Java XML lib 直接染指 parameter 的 defaultValueExpression。

比較好的方法是用 JasperReports API
JasperDesign jasperDesign = JRXmlLoader.load(designFile);
Map<String, JRDataset> datasetMap = jasperDesign.getDatasetMap();
Object[] names = datasetMap.keySet().toArray();
for (Object name : names) {
    JRDesignDataset dataset = (JRDesignDataset)datasetMap.get((String)name);
    
    jasperDesign.removeDataset(dataset);
    
    Map<String, JRParameter> parametersMap = dataset.getParametersMap();
    if (!parametersMap.containsKey(STOCK_CODE)) {
        continue;
    }
    JRDesignParameter stockCodeParameter = (JRDesignParameter)parametersMap.get(STOCK_CODE);
    JRDesignExpression expression = new JRDesignExpression(stockCode);
    stockCodeParameter.setDefaultValueExpression(expression);
    jasperDesign.addDataset(dataset);
}
不怎麼好看,但這是我想出來還算可以的辦法。

如果只是染指 main report 的 parameter,那倒是很容易:
HashMap<String, Object> parameters = new HashMap<String, Object>();
parameters.put(STOCK_CODE, stockCode);
JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, conn);
超簡單,也超乾淨的。



1.2 Execution Phase

Execution phase 與 design phase 是相輔相成的。簡單說,就是直接在 iReport 按 Preview,記得將 Preview 設成 Internal Preview,增加開發速度。



1.3 Export Phase 

Reference: http://community.jaspersoft.com/wiki/exporting-reports

在 iReport 將 Preview 設成 HTML Preview,按 Preview,然後按下 Save icon 就可以了。但這樣是沒辦法用程式自動化的 ((點 UI 真是可怕))。



1.4 Deploying Phase

為了讓程式自動產生報表,我必須重操舊業,重新複習生澀的 Java。我很感謝前同事 Taco 教我許多 Java programming 知識。

Reference: http://community.jaspersoft.com/wiki/deploying-reports


先來看我用哪些東西:
  • Java 開發環境: Eclipse。Eclipse IDE for Java EE Developers,有 Windows 版本跟 Mac 版本,只要解壓縮完就可以直接用了。
  • Java build tool: Apache Ant
  • Java argparse: Commons CLI。用過 Python argparse 都知道 argparse 真是有夠好用的,因此我想找相對應的 argparse。
  • Java logging: log4j。用過的都說好。
  • Java configuration: Commons Configuration

接著關心一下引用哪些 Java library ((以 jar 檔的方式包裝)):

以上是概說。


寫程式的過程,十足讓我傷透腦筋。我手上有 JRXML JasperReports 設計檔,但 JRXML 缺少 data source 設定,原先由 iReport 全權處理,這次得自己處理這段。
    private Connection getConn() throws Exception {
        Class.forName(config.getString("database.driver"));        
        String url = config.getString("database.url");
        String username = config.getString("database.username");
        String password = config.getString("database.password");
        return DriverManager.getConnection(url, username, password);    
    }
其中 config 就是 org.apache.commons.configuration.XMLConfiguration,設定檔內容是
<?xml version="1.0" encoding="UTF-8"?>

<config>
    <database>
        <driver>org.postgresql.Driver</driver>
        <url>jdbc:postgresql://localhost:5432/database</url>
        <username>username</username>
        <password>password</password>
    </database>
    <report>
        <designFile>designFile.jrxml</designFile>
        <destFilePathTemplate>report\designFile-%s.html</destFilePathTemplate>
    </report>
</config>
關於 Class.forName() 怪招,可以參考 Establishing a Connection,細節頗多,留意一下。


接著是 iReport 產出報表,這段得用 Java 來做。關於最困難的部分,設定 parameters,前面大致講過,就不再重複。
JasperDesign jasperDesign = JRXmlLoader.load(designFile);
JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign);

Connection conn = getConn();

JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, conn);
JasperExportManager.exportReportToHtmlFile(jasperPrint, reportFile);

conn.close();
基本骨幹就是這樣。

其他細節偏題頗遠,將會另開文章討論 ((Python 如何抓資料、PostgreSQL、JRXML、更詳細的 Java 程式解說,等等))



附註:
[1] ReportGenerator.java
package stocktotal.report;

import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.HashMap;
import java.util.Map;

import net.sf.jasperreports.engine.JRDataset;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.design.JRDesignDataset;
import net.sf.jasperreports.engine.design.JRDesignExpression;
import net.sf.jasperreports.engine.design.JRDesignParameter;
import net.sf.jasperreports.engine.design.JasperDesign;
import net.sf.jasperreports.engine.xml.JRXmlLoader;

import org.apache.commons.configuration.XMLConfiguration;
import org.apache.log4j.Logger;

public class ReportGenerator {
    
    public void generate(String stockCode, String configFile) throws Exception {
        this.config = new XMLConfiguration(configFile);        

        logger.info("Prepare jrxml for JasperDesign");
        String designFile = config.getString("report.designFile");
        this.jasperDesign = JRXmlLoader.load(designFile);

        logger.info("Set parameters for sub dataset");
        setDatasetStockCode(stockCode);
        
        logger.info("Compile to JasperReport file");
        jasperReport = JasperCompileManager.compileReport(jasperDesign);
        
        logger.info("Prepare parameters for main report");
        HashMap<String, Object> parameters = new HashMap<String, Object>();
        parameters.put(STOCK_CODE, stockCode);
        
        logger.info("Prepare datasource for main report");
        Connection conn = getConn();
        
        logger.info("Set prepared parameters and datasource for JasperReport");
        JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, conn);
        
        logger.info("Export to HTML format report");
        makeReportDirectory(stockCode);
        JasperExportManager.exportReportToHtmlFile(jasperPrint, getReportFilePath(stockCode));

        logger.info("Release datasource connection");
        conn.close();
    }

    private void setDatasetStockCode(String stockCode) throws Exception {
        Map<String, JRDataset> datasetMap = jasperDesign.getDatasetMap();
        Object[] names = datasetMap.keySet().toArray();
        for (Object name : names) {
            JRDesignDataset dataset = (JRDesignDataset)datasetMap.get((String)name);
            
            jasperDesign.removeDataset(dataset);
            
            Map<String, JRParameter> parametersMap = dataset.getParametersMap();
            if (!parametersMap.containsKey(STOCK_CODE)) {
                continue;
            }
            JRDesignParameter stockCodeParameter = (JRDesignParameter)parametersMap.get(STOCK_CODE);
            JRDesignExpression expression = new JRDesignExpression(stockCode);
            stockCodeParameter.setDefaultValueExpression(expression);
            jasperDesign.addDataset(dataset);
        }        
    }
    
    private Connection getConn() throws Exception {
        Class.forName(config.getString("database.driver"));        
        String url = config.getString("database.url");
        String username = config.getString("database.username");
        String password = config.getString("database.password");
        return DriverManager.getConnection(url, username, password);    
    }
    
    private void makeReportDirectory(String stockCode) {
        String reportFilePath = config.getString("report.destFilePathTemplate");
        String reportDir = (new File(reportFilePath)).getParent();
        (new File(reportDir)).mkdir();
    }
    
    private String getReportFilePath(String stockCode) {
        return String.format(config.getString("report.destFilePathTemplate"), stockCode);
    }
    
    final static Logger logger = Logger.getLogger(ReportGenerator.class);
    
    private String STOCK_CODE = "STOCK_CODE";
    
    private XMLConfiguration config;
    
    private JasperDesign jasperDesign;
    
    private JasperReport jasperReport; 
}

[2] Program.java ((程式進入點))
package stocktotal.report;

import org.apache.log4j.Logger;

public class Program {

    public static void main(String[] args) {
        try {
            Argparser parser = new Argparser();
            parser.parse(args);
            String stockCode = parser.getStockCode();
            String configFile = parser.getConfigFile();
            
            logger.info(String.format("Stock Code: %s", stockCode));
            logger.info(String.format("Config File: %s", configFile));

            ReportGenerator generator = new ReportGenerator();
            generator.generate(stockCode, configFile);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    final static Logger logger = Logger.getLogger(Program.class);

}

[3] Argparser.java
package stocktotal.report;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

public class Argparser {
    
    @SuppressWarnings("static-access")
    public Argparser() {
        Option help = new Option("help", "print this message");
        Option stockCode = OptionBuilder.withArgName("stockCode")
                .hasArg()
                .withDescription("set stock code")
                .create("stockcode");
        Option configFile = OptionBuilder.withArgName("file")
                .hasArg()
                .withDescription("use given file for config")
                .create("configfile");
        
        this.options = new Options();
        options.addOption(help);
        options.addOption(stockCode);
        options.addOption(configFile);
        
        this.parser = new GnuParser();
    }
    
    public void parse(String[] args) throws ParseException {
        this.cmdline = this.parser.parse(this.options, args);
    }
    
    public String getStockCode() {
        assert this.cmdline != null;
        return cmdline.hasOption("stockcode") ? cmdline.getOptionValue("stockcode") : null;
    }
    
    public String getConfigFile() {
        assert this.cmdline != null;
        return cmdline.hasOption("configfile") ? cmdline.getOptionValue("configfile") : null;
    }

    private Options options;
    
    private CommandLineParser parser;
    
    private CommandLine cmdline;
}


[4] log4j.xml ((放在 src 目錄底下))
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">

    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{2} %x - %m%n"/>
        </layout>
    </appender>
        
    <appender name="logfile" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="file" value="logfile/logfile.log" />
        <param name="append" value="true" />
        <param name="datePattern" value="'.'yyyy-MM-dd" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{2} %x - %m%n"/>
        </layout>
    </appender>    
    
    <root>
        <level value="INFO"/>
        <appender-ref ref="console"/>
        <appender-ref ref="logfile"/>
    </root>

</log4j:configuration>

2012年11月25日 星期日

《猜火車》


故事可以從這邊說起。

讓我想起以前看猜火車覺得他們很屌,沒想到,現在自己也被提名最佳男主角
-- MC Hotdog 我的生活

總之就是奇怪的機緣,總要來朝聖一下這本書寫什麼《猜火車》。表面上是爛屁股情節,實際上作者將許多無奈、憤慨、發泄、忍辱、吶喊、詛咒、領悟、同情、現實寄情於不存在拼湊的故事情節,想一想,好像生活欠缺許多真實。


然後天殺的金錢又贏了最後一場比賽。

2012年11月22日 星期四

《少年PI的奇幻漂流》


墾丁白榕園 (須申請):
http://www.uukt.com.tw/phpbb/viewtopic.php?t=53536&sid=76d7d0edd1302c38ea50e36ea2fd48b2


先前走過聖稜線,有時走在隊伍最後面 ((隊伍總共三個人)),就得獨自面對一個人的孤單。自己心靈跟自己身體對話,以前看不清的事,例如求生的意志,在那個時刻突然了解人為什麼要求生,除了活下來,也想好好跟家人及朋友道別,說點感謝的話。


當然啦,那些只是突然的念頭,山屋不一會兒就到了,而且身體也能負荷,只是那個時刻有點兒辛苦。中間 PI 看到貨輪出現在水平面,他拼命發出求救訊號,貨輪卻沒發現,這也讓我想起北二O第二天老母雞試圖救援卻功虧一簣的情景,談不上絕望,但失落感頗重倒是真的,這點跟 PI 心境或許有點相像吧。


除去驚人的 3D 特效 ((他媽的好漂亮)),整部片最有意思的是「兩個故事」。看完第一個故事的美好,把第二個故事重新套一次,不禁讓人心驚,孟加拉虎,不過是另一個自己的寫照,這隻臭貓,害我又哭了。

2012年11月19日 星期一

騷擾


前女友騷擾不下十幾次,我又不是免洗筷,挾了就丟。


更何況,免洗筷,也是要錢的。四年過去了,還想怎樣。上次亂翻我東西,我不生氣,雖然我應該要生氣。套句三個傻瓜的台詞,aal izz well,拜託別再騷擾我了,不能用免洗筷挾菜,可以用手抓呀。


PS. 如何用 iPhone 4S 設黑名單,虧作者想到這招。

2012年11月17日 星期六

[PostgreSQL] 重寫 ROE query


PostgreSQL 語法比 SQLite 龜毛的多,也就是嚴謹的多。會這樣寫,主要是為了接上 iReport,使用 Timeseries Chart 畫圖,Dataset 的 query 如下,這樣我們就能拉出四個 Time series,這樣就很直覺了。



select
    U.activity_date,
    U.annual_adjusted_net_income/U.shareholder_equity as roe,
    U.annual_adjusted_net_income/U.annual_adjusted_operating_income as net_profit_margin,
    U.annual_adjusted_operating_income/U.total_assets as total_assets_turnover,
    U.total_assets/U.shareholder_equity as equity_multiplier
from
(
    select
        T.activity_date,
        T.shareholder_equity,
        case
            when date_part('month', T.activity_date) = 3 then T.net_income * 4/1
            when date_part('month', T.activity_date) = 6 then T.net_income * 4/2
            when date_part('month', T.activity_date) = 9 then T.net_income * 4/3
            else T.net_income
        end as annual_adjusted_net_income,
        case
            when date_part('month', T.activity_date) = 3 then T.operating_income * 4/1
            when date_part('month', T.activity_date) = 6 then T.operating_income * 4/2
            when date_part('month', T.activity_date) = 9 then T.operating_income * 4/3
            else T.operating_income
        end as annual_adjusted_operating_income,
        T.total_assets,
        max(T.report_date)
    from
    (
        select
            A.activity_date,
            A.report_date,
            A.number as shareholder_equity,
            B.number as net_income,
            C.number as operating_income,
            D.number as total_assets
        from
            BalanceSheet as A,
            IncomeStmt as B,
            IncomeStmt as C,
            BalanceSheet as D
        where
        A.stock_code = B.stock_code
        and B.stock_code = C.stock_code
        and C.stock_code = D.stock_code
        and A.activity_date = B.activity_date
        and B.activity_date = C.activity_date
        and C.activity_date = D.activity_date
        and A.item = '股東權益總計'
        and B.item = '合併總損益'
        and C.item = '營業收入合計'
        and D.item = '資產總計'
        and A.report_type = 'C'
        and B.report_type = 'C'
        and C.report_type = 'C'
        and D.report_type = 'C'
        and A.stock_code = '2498'
    ) as T
    where
        T.shareholder_equity != 0
        and T.net_income != 0
        and T.operating_income != 0
        and T.total_assets != 0
    group by
        T.activity_date,
        T.shareholder_equity,
        T.net_income,
        T.operating_income,
        T.total_assets
    order by T.activity_date
) as U


但接下的問題是四個 time series 通通共用一個 axis,如果要拆開成 primary axis 與 secondary axis ((Excel 用語)),我們得用 Multi Axis Chart 當湯底,兩個 Timeseries Charts 當材料,Axis Position 取「Right or Bottom」或是「Left or Top」。



然後繼續做圖表,又用一些好招式:用 Parameters 變化各種圖表。

((這個是資本結構的 query,當做另一個 dataset))

select
    U.activity_date,
    U.equity/U.assets as equity_ratio,
    U.liabilities/U.assets as liabilities_ratio,
    U.assets/U.equity as equity_multiplier
from
(
    select
        T.activity_date,
        T.assets,
        T.liabilities,
        T.equity,
        max(T.report_date)
    from
    (
select
            A.activity_date,
            A.report_date,
            A.number as assets,
            B.number as liabilities,
            C.number as equity
        from
            BalanceSheet as A,
            BalanceSheet as B,
            BalanceSheet as C
        where
        A.stock_code = B.stock_code
        and B.stock_code = C.stock_code
        and A.activity_date = B.activity_date
        and B.activity_date = C.activity_date
        and A.item = '資產總計'
        and B.item = '負債總計'
        and C.item = '股東權益總計'
        and A.report_type = 'C'
        and B.report_type = 'C'
        and C.report_type = 'C'
        and A.stock_code = $P{STOCK_CODE}
    ) as T
    group by
        T.activity_date,
        T.assets,
        T.liabilities,
        T.equity
    order by T.activity_date
) as U
where
    U.assets != 0

2012年11月16日 星期五

先先


宗緯跟我說這禮拜「先先」,就是提不起精神的樣子。


我呢,跟北二段比起來,現在算很有精神,也比較能夠接受北二段稜線的美景。究竟是人太多情,山哪管那麼多,祂就是山咩。


D1 (2012-11-08)
  • 0615 武陵山莊
  • 0800 池有登山口
  • 1300 三叉營地,午餐
  • 1350 出發
  • 1410 池有山第一登山口
  • 1420 池有山第二登山口
  • 1440 ▲池有山
  • 1530 池有名樹
  • 1630 新達山屋

D2 (2012-11-09)
  • 0300 起床
  • 0400 出發
  • 0510 ▲品田山前峰,睡覺等兩個人
  • 0730 回新達山屋,整理
  • 0905 出發
  • 1050 展望處,可見大小霸、東霸五連峰
  • 1130 塔克金溪,午餐
  • 1225 出發
  • 1425 巴紗拉雲山屋
  • 1600 霸南山屋

D3 (2012-11-10)
  • 0400 起床
  • 0520 出發
  • 0620 I LOVE YOU平台
  • 0635 煙囪地形
  • 0645 大霸尖山登山口,禁止攀爬
  • 0700 小霸尖山三叉路口
  • 0745 ▲小霸尖山
  • 0835 大霸尖山霸基取水
  • 0850 小風口
  • 1000 中霸山屋
  • 1250 先行折回再取水
  • 1400 小霸尖山三叉路口
  • 1430 等到兩個人,回霸南山屋
  • 1600 霸南山屋

D4 (2012-11-11)
  • 0400 起床
  • 0525 出發
  • 0635 巴紗拉雲山屋,休息十分鐘
  • 0800 ▲巴紗拉雲山
  • 0950 ▲布秀蘭山
  • 1215 ▲素密達山,不用午餐省時間
  • 1230 素密達斷崖
  • 1410 全員通過
  • 1440 素密達山屋

D5 (2012-11-12)
  • 0500 起床
  • 0635 出發
  • 0740 地形,走不完的地形
  • 0825 穆南營地
  • 1005 雪山北峰叉路口
  • 1015 ▲雪山北峰,玩耍一下
  • 1040 雪山北峰叉路口,繼續走可怕的天氣
  • 1100 雪北山屋,午餐
  • 1200 出發
  • 1430 ▲凱蘭特崑山
  • 1445 水管路回三六九山莊,大家在此猶豫是否下切,不取
  • 1630 ▲北稜角
  • 1750 翠池山屋

D6 (2012-11-13)
  • 0400 起床
  • 0515 出發
  • 0620 鞍部,取捷徑下切圈谷,不取雪山主峰
  • 0710 圈谷解說牌,中間卡在樹叢中,仔細找路後脫困
  • 0830 三六九山莊,休息二十分鐘
  • 0935 雪山東峰登山口,略過不取,懶惰
  • 1020 哭坡
  • 1135 七卡山莊
  • 1230 大水池登山口

2012年11月14日 星期三

2012年11月5日 星期一

可怕的夢


今天夢到她告訴我,不要來找我。可怕。



渾然天成的渾水摸魚的渾渾噩噩。



最後補一發奇文。

財金(評) 101-232 號
November 05, 2012
馬英九是有政績的總統 
中國國民黨中央委員/國政基金會特約研究員 鄧哲偉
關鍵字: 政績 馬英九

中國國民黨第18屆中央委員會及中央評議委員會第4次全體會議於11月3日召開,因為國際經濟萎靡,台灣經濟也連帶不振,馬總統施政滿意度創新低,造成黨內許多質疑的聲音。事實上,我認為馬英九是一個有政績的總統,勇於任事不畏艱難,是一位追求改革的先鋒,雖然短期造成衝擊不小,但其作為將會奠定台灣的百年盛世。

馬英九之所以是有政績的總統,最令人敬佩就是他的態度-衝、衝、衝。看似溫儒如雅的書生,談到改革,就有拿著竹竿去捅蜂窩的勇敢。他當上黨主席後,就推動處分黨產,裁減黨工,推動國民黨轉型,讓國民黨更貼近民意,這些都是不可能的任務,但他做到了。當上了總統,不畏軍公教是國民黨的大票倉,推動取消軍公教免稅、取消軍公教年終慰問金;也不畏營造業是選舉的金主,為了打房,他推動奢侈稅,成功減緩房地產的飆升;更不畏財金界的抗議,力挺部屬,首度成功推動證所稅。馬英九衝、衝、衝的態度,勇於改革,深具魄力,同為黨主席的蘇貞昌,相形遜色許多。

馬英九之所以是有政績的總統,在於動盪的局勢創造平凡的環境。他執政的這四年,面對國際能源、金屬及糧食接連的大漲,全球物價劇升,但台灣這幾年物價上漲率一直控制在3%以下,匯率也相當的穩定,表現亮眼。在危機中依然給台灣維持物美價廉的經濟環境,相當不容易。過去台灣人去大陸覺得東西便宜,現在卻相反,大陸人來台灣覺得東西便宜,這就是偉大卻平凡的政績

馬英九之所以是政績的總統,因為他在外交及兩岸上走中道路線,不極端、不躁進、不迴避。在最重要的兩岸關係上,以穩定和諧的腳步,讓兩岸交流達到前所未有的境界。在外交上更有重大成就,雖然台灣只有23個邦交國,但免簽證的國家及地區就有129個,尤其是美國免簽,台灣是美國唯一發給免簽的非邦交地區,大陸邦交國有171個,卻只有20多個國家有免簽待遇。馬英九真的厲害。

馬英九之所以是有政績的總統,因為他真的沒有結黨營私,更沒有所謂的馬家軍。他自律甚嚴,用人唯才,選舉團隊不等於執政團隊,跟著他的幕僚,心中就要自覺,要有不求升官發財的心態,工作卻要二十四小時待命的決心。因為沒有小圈圈,也沒有馬家軍,這一連串政策的推動,不僅在野黨不支持,國民黨也欠缺強而有力的辯護聲音,馬英九就必須一個人概括承受。

馬英九是一個令人放心的總統,因為他娶到一個好太太-周美青。一個成功的男人,背後一定要有一個偉大的女人。周美青的一舉一動,都每分每秒的為馬英九加分。因為看見周美青,所以我們相信馬英九在妻管嚴的狀況下,是不會作出對台灣不利的事。

台灣人民對於馬英九的要求,是永無止盡的。施政滿意度的高低,不代表政府決策的對與錯。事實上,我們用宏觀的角度看待馬英九,我們用大陸人民或是世界各國的角度來看待馬英九,台灣的人民是幸福的。馬英九的態度及格局,是世界各國所少見的,時間絕對會證明馬英九是台灣有政績的總統。

(本文僅代表作者個人意見,不代表本會立場)
(本文發表於2012年11月5日中央網路報)



幹。我是在幹總統,不是幹馬英九。

2012年11月3日 星期六

PostgreSQL 我的第一次


Installation: http://www.postgresql.org/download/macosx/

選 one click installer,這樣才有好的 management tool 可以用。內建好像是 pgAdmin3。我們除了是 users,也是 administrators ((一人公司的缺點))



Python 3 interface: http://pypi.python.org/pypi/py-postgresql/1.1.0
>>> import postgresql
>>> db = postgresql.open('pq://postgres:***@localhost:5432/postgres')
>>> db.close()
>>> print(db)
<postgresql.driver.pq3.Connection[pq://postgres:***@localhost:5432/postgres] closed>
>>> 


接著就是為 stocktotal 開帳號跟資料庫了。還好我用過 Microsoft SQL Server,幾乎是一樣的方式。

2012年11月1日 星期四

[Python] Install Library on Mac


numpy: http://www.scipy.org/Installing_SciPy/Mac_OS_X
xlrd: http://pypi.python.org/pypi/xlrd3
lxml: http://pypi.python.org/pypi/lxml/3.0.1
matplotlib: https://github.com/matplotlib/matplotlib


大多是 git clone 抓檔案或是 tar -zxf 解壓縮。接著 python3 setup.py build ((若需要的話)) 及 python3 setup.py install。在 Mac 上作這些事反而相對輕鬆,有點意外。