Android DataStore Java (RxDataStore)

Google menyarankankan agar SharedPreference diganti dengan Datastore. Alasan utamanya adalah SharedPref dijalankan di main thread sehingga dapat mengganggu kinerja user interface.

Sayangnya, kebanyakan tutorial menggunakan Kotlin, bahkan di website Android sendiri pembahasannya terbatas. Saya mencoba membuat contoh sederhana tentang RxDataStore ini.

Tambahkan di gradle

   implementation("androidx.datastore:datastore-rxjava3:1.0.0")

Tambahkan satu class Singleton agar datastore dapat diakses di lebih dari satu activity.

public class SingletonDatastore {

    private static SingletonDatastore INSTANCE = null;

    public RxDataStore<Preferences> dataStore;
    public Preferences.Key<String> NAMA_KEY = PreferencesKeys.stringKey("nama_key");

    private SingletonDatastore(Context ctx) {
        dataStore =
                new RxPreferenceDataStoreBuilder(ctx, /*name=*/ "settings").build();
    };

    public static SingletonDatastore getInstance(Context ctx) {
        if (INSTANCE == null) {
            INSTANCE = new SingletonDatastore(ctx);
        }
        return(INSTANCE);
    }

    public void setVal(String val) {
        Single<Preferences> updateResult =  dataStore.updateDataAsync(prefsIn -> {
            MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
            mutablePreferences.set(NAMA_KEY, val);
            return Single.just(mutablePreferences);
        });
    }


Untuk menggunakannya, di MainActivity Java, saya menambahkan dua button, set dan get. Kuncinya menggunakan subscriber onNext untuk mendapatkan data yang tersimpan di datastore.

public class MainActivity<nama_key> extends AppCompatActivity {

    private TextView tvHasil;
    SingletonDatastore st;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvHasil= findViewById(R.id.tvHasil);
        st = SingletonDatastore.getInstance(getApplicationContext());
    }

    //menulis nilai ke datastore
    public void onClickSet(View v) {
       st.setVal("Wati Hartawati");
    }

    //baca isi datastore dan menuliskannya ke textview
    public void onClickGet(View v) {
        Flowable<String> flowNama =
                st.dataStore.data().map(
                        prefs -> prefs.get(st.NAMA_KEY));

        flowNama.subscribe(new DisposableSubscriber<String>() {
            @Override
            public void onNext(String s) {
                tvHasil.setText(s); //hasil tersimpan di sini
            }

            @Override
            public void onError(Throwable t) {
                Log.d("debug_yudi",t.getMessage());
            }

            @Override
            public void onComplete() {
                 Log.d("debug_yudi","complete");
            }
        });
    }
}
Iklan

Pengganti AsyncTask

Semester ini saya mulai mengajar lagi mobile app dev dengan Android (native). Saat saya update modul lama saya dibagian multi threading, ternyata saya menemukan bahwa AsyncTask sudah di-deprecated

“This class was deprecated in API level 30. Use the standard java.util.concurrent or Kotlin concurrency utilities instead.”

Sialnya Google tidak memberikan tutorial atau contoh pengganti AsyncTask ini, khususnya untuk Java. Materi kuliah saya masih dalam Java bukan Kotlin karena setelah saya lihat TiobeIndex, peringkat Kotlin masih jauh dibandingkan Java. Bagi mahasiswa, penguasaan Java masih penting saat mereka lulus nanti. Saya juga lihat lowongan yang eksklusif menggunakan Kotlin masih jarang dan di sisi lain Flutter semakin banyak.

Aplikasi contoh yang saya buat ini sederhana. Ada satu thread, saat thread dijalankan, dia akan mengupdate user interface, demikian juga setelah selesai. Thread dapat dibatalkan. Jadi ada tombol memulai thread dan ada tombol membatalkan thread. Hal penting yang sering dilupakan yang sering memunculkan bug: dapat menangani state saat device di-rotate.

Setelah dua hari mencoba, saya bisa mengimplementasikan menggunakan FutureTask, Executor, ViewModel+LiveData dan Handler.SendMessage. Tolong infokan jika ada saran implementasi yang lebih elegan 🙂

Berikut code-nya, penjelasan ada di comment di dalam code.

MainActivity.Java

public class MainActivity extends AppCompatActivity {

    TextView tvHasil;
    private  HasilViewModel model;
    private FutureTask ft;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvHasil = findViewById(R.id.tvHasil);
        model = new ViewModelProvider(this).get(HasilViewModel.class);

        //observer agar bisa update otomatis saat data di model berubah
        final Observer<String> observerPesan = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String pesan) {
                tvHasil.setText(pesan);
            }
        };

        //daftarkan observer ke model, akan dipanggil jika data di model berubah
        model.getData().observe(this,observerPesan);

    }

    public void onClickCancel(View v) {
        //ambil dari viewmodel karena saat di rotate
        //referensi ke future hilang (thread tetap jalan tapi tdk bisa distop)
        ft = model.ft;
        ft.cancel(true);  //cancel
    }

    public void onClickMulai(View v) {
        Log.d("dyw","Mulai");
        tvHasil.setText("Mulai..");
        //hanya single thread dalam satu waktu
        ExecutorService executor = Executors.newSingleThreadExecutor();

        //harus di oncreate biar saat diroatet tidak hilang
        ft = new FutureTask<String>(new Runnable() {
            @Override
            public void run() {
                Log.d("dyw","Masuk thread");

                //ambil main atau UI thread
                Handler handler = new Handler(Looper.getMainLooper())  {
                    @Override
                    public void handleMessage(Message inputMessage) {
                        //update model --> UI juga akan terupdate
                        //jika tdk menggunakan viewmodel saat dirotate
                        //maka UI tidak akan bisa udpate walau thread
                        // tetap jalan
                        model.setPesan((String)(inputMessage.obj));
                    }
                };

                boolean isStop = false; //jika di cancel, stop
                for (int i=0;i<=10;i++) {
                    try {
                        if (isStop) {
                            Log.d("dyw","Stop, keluar dari loop");
                            break;}
                        // log untuk memudahkan debug
                        Log.d("dyw", String.valueOf(i));
                        Thread.sleep(1000);

                        //update UI untuk progress
                        //jenggunakan sendMessage
                        //lihat method handledMessage di atas
                        Message myMessage = handler.obtainMessage();
                        myMessage.obj = String.valueOf(i);
                        handler.sendMessage(myMessage);

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        Log.d("dyw","interrupt! thread di-cancel");
                        isStop = true;
                    }
                }
                //selesai, kirim pesan ke UI
                String pesan = "Sukses";
                if (isStop) {
                    pesan = "Distop manual!";
                }
                Message myMessage = handler.obtainMessage();
                myMessage.obj = String.valueOf(pesan);
                handler.sendMessage(myMessage);
            }
        },null);

        model.ft = ft;       //simpan future ke viewmodel sehingga bisa dicancel walau dirotate
        executor.submit(ft); //jalankan future thread

        //kalau executor di terminateed manual akan ada efek aneh
        //jalan tapi langsugn terminated, baru yg kedua normal
    }
}

HasilViewModel.java

public class HasilViewModel extends ViewModel {
    private String pesan;

    public HasilViewModel() {
        pesan = "-";
    }

    private MutableLiveData<String> data;
    public FutureTask ft;  //supaya bisa di-cancel setelah rotate

    //class lain mengakses data melalui ini
    //perhatikan ini LiveData, bukan MutableLiveData
    public LiveData<String> getData() {
        //jika belum ada, create
        if (data == null) {
            data = new MutableLiveData<String>();
            data.setValue(pesan);
        }
        return data;
    }

    public void setPesan(String pesan) {
        data.setValue(pesan);
    }

}

Repo github: https://github.com/yudiwbs/MultiThread

Apartemen Dago Suites

Saya menulis posting ini karena ternyata sangat sedikit materi di internet tentang apartemen Dago Suites, jadi semoga artikel ini bisa membantu, terutama bagi tipe orang yang yang hobi googling habis-habisan mencari informasi sebelum membeli sesuatu 🙂 Ini akan jadi posting yang panjang karena terus diupdate. Silakan skip kalau tidak menyukai tipe posting seperti ini hehe.

Sebenarnya rencana untuk membeli apartemen dekat ITB sudah ada sejak anak diterima kuliah di ITB. Ditambah ibunya yang kerja di ITB, Bandung yang semakin macet sehingga jalur Sarijadi > ITB > UPI > ITB > Sarijadi jadi semakin menghabiskan waktu dan tenaga. Apartemen bisa menjadi tempat kos anak, dan “markas” saya dan istri saat menunggu jam macet lewat.

Akibat pandemi, rencana tersebut ditunda dan akhirnya baru bisa terlaksana bulan September 2022. Apartemen yang berada dekat ITB hanya dua: Dago Suites dan Beverly Dago. Keduanya berada di Jalan Sangkuriang (Cisitu) dan berjarak sekitar 750m dari gerbang utara ITB, jadi hanya sekitar 20 menit berjalan kaki santai.

Kelebihan lain apartemen ini adalah berada di Jalan Sangkuriang yang relatif sepi dibandingkan jalan Cisitu. Jadi tidak perlu khawatir macet tidak bisa keluar dari apartmen dan suasananya lebih sepi terutama di malam hari.

Kedua apartemen ini dikembangkan oleh pengembang yang sama yaitu Istana Group. Perbedaan utama dari dua apartemen ini adalah semua unit di Beverly Dago bertipe studio sedangkan Dago Suites, selain studio juga ada tipe 1BR sampai 3BR. Bahkan kalau dilihat dari rencana awal, sepertinya Dago Suites ini dirancang tidak memiliki tipe studio (itu sebabnya namanya berakhiran Suites?).

Zoom Beverly Dago & Dago Suites

Dapat dilihat pada foto di atas, tidak ada informasi lalulintas di Google Map untuk jalan Sangkuriang (karena terlalu sepi) dan Beverly lebih ramai dibandingkan Dago Suites (berwarna kuning).

Lobi Beverly lebih kecil dan sederhana dibandingkan Dago Suites. Tempat parkir, lift Dago Suites juga lebih lebar dan lebih banyak. Harga unit Studio Dago Suites relatif lebih mahal dibandingkan Beverly. Saya lihat unit di Beverly lebih banyak disewakan harian seperti di AirBnB. Mungkin ini yang membuat Beverly relatif lebih ramai. Kami memilih Dago Suites karena lebih cocok dengan suasananya.

Lobi Dago Suites
Koridor Dago Suites

Untuk selanjutnya saya hanya akan membahas Dago Suites, karena kami mendatangi Beverly hanya sebentar saat survey beberapa unit.

Di Dago Suites, tersedia dua set lift (4 & 2 lift per set), total 6 lift(?) + 1 lift service. Hanya satu set yang dapat mengakses GF dari tempat parkir B1-B3, yaitu set lift yang berada di sisi barat. Lift service tidak bisa digunakan oleh penghuni. Khusus untuk lantai 1 dan 2 tersedia tangga langsung ke GF. Kartu akses perlu untuk tiap lantai.

Akses tangga ke GF dari lt 1 dan 2

Fasilitas umum yang disediakan adalah ruang bersama, parkir, kolam renang dan gym.

Ruang bersama (di foto tangga di atas, ruangan yang memiliki sofa) dapat digunakan dengan cara dipesan terlebih dulu. Cocok untuk belajar bersama dan dapat memuat belasan orang. Gym dan kolam renang belum saya coba.

Gym

Untuk parkir, bayarannya per jam, tanpa batas maksimal (hati-hati untuk yang parkir lama). Ada voucher 10rb/hari (saya belum tahu syaratnya). Untuk penghuni bisa membeli akses parkir 100rb/bulan, dan bisa langsung isi sekaligus beberapa bulan. Saya suka dengan tempat parkir dekat tiang yang relatif lega (di beberapa tempat lain yang saya pernah datangi, umumnya jarak tiang ke mobil sempit). Dari diskusi dengan petugas, jarang tempat parkir penuh. Jadi tidak perlu khawatir dengan kasus parkir paralel. Mungkin ini karena penghuni Dago Suites kebanyakan mahasiswa yang tidak memerlukan kendaraan dan unit yang disewakan harian relatif lebih sedikit.

Flutter

Semester ini diminta mengampu matakuliah pemrograman visual. Matakuliah ini dulunya diarahkan untuk pemrograman desktop (masa pemrograman dengan UI teks masih dominan). Tetapi mengingat sekarang desktop sudah semakin tergeser dengan web dan mobile, maka saya ganti toolsnya dengan Flutter.

Saya tertarik dengan Flutter karena satu code bisa digunakan untuk Android, iOS, Web dan desktop. Flutter (tepatnya Dart) juga mendukung JIT dan AOT, jadi kinerjanya bagus tapi saat debug juga enak, bisa langsung hot reload.

Waktunya juga pas, saya memang ada rencana membuat aplikasi mobile untuk keperluan pusat data kampus dan aplikasi tentang saham. Apalagi saya mulai bosan dengan native android dev yang terasa tambal sulam dengan Java digantikan Kotlin. Saya juga malas menggunakan jscript untuk pemrograman web hehe.

Kelemahan Flutter adalah menggunakan bahasa Dart yang kurang populer dan “dimiliki” oleh Google (open source sih, tapi kalau Google mencabut support?). Semoga saja Flutter masih ada dalam beberapa tahun ke depan.

Modul praktikum Dart dan Flutter yang saya buat (masih belum selesai karena masih belajar): https://docs.google.com/document/d/16bTPjc68cBHhBoFq1VeMBJZ9PovceT8D-iptAp7wL-o/edit?usp=sharing

Data Fundamental dan Market dengan yfinance dan Analisis Teknikal dengan TA-Lib

Quantitative trading mengenal tiga jenis data:

  1. Data fundamental emiten yang didapat dari laporan keuangan emiten dan data fundamental lain seperti harga komoditas, inflasi, suku bunga, PMI (Purchasing Manager Index), dll.
  2. Data market, yang bersumber dari transaksi order beli/jual.
  3. Data tambahan seperti berita, foto satelit, video dan media sosial.

Dalam tulisan ini saya akan membahas data fundamental emiten dan data market yang dapat diperoleh dengan cara yang paling mudah dan gratis. Saya menggunakan Python dan Jupyter Notebook.

Untuk data fundamental suatu emiten, dapat kita peroleh menggunakan library yfinance yang menggunakan data dari Yahoo Finance. Install yfinance. Lalu gunakan code berikut untuk mengambil data UNVR sebagai contoh.

import yfinance as yf
from IPython.core.display import display
unvr = yf.Ticker('UNVR.JK')
unvr.info

Terlihat banyak sekali informasi yang bisa diambil, mulai dari sektor, alamat, pertumbuhan pendapatan dan lain-lain. Untuk mengakses datanya:

print(unvr.info["sector"])
print(unvr.info["dividendYield"])
print(unvr.info["priceToBook"])

Selain itu tersedia data-data lain seperti cash flow, balance sheet, earning. Berikut contoh untuk informasi cashflow.

#dataframe
display(unvr.cashflow)  
#lihat indeksnya
display(unvr.cashflow.index) 
#akses salah satu atribut
display(unvr.cashflow.loc["Total Cash From Operating Activities"]) 

Dokumentasi lengkap bisa dilihat di: https://github.com/ranaroussi/yfinance

Berikutnya adalah data market, idealnya memang kita bisa mendapat data rincian order beli dan jual sehingga semua proses di bursa bisa ditangkap, tapi data ini mahal. Untuk mendapatkan data OHLCV (Open, High, Low, Close, Volume) dapat digunakan yfinance sbb:

unvr_market = yf.download('UNVR.JK','2021-01-01','2021-11-7', auto_adjust=True)
unvr_market

Selanjutnya kita bisa menggunakan TA-LIB untuk mendapatkan berbagai analisis teknikal. Install TA-LIB terlebih dulu (ini untuk Linux, untuk OS lain bisa dilihat caranya di: https://github.com/mrjbq7/ta-lib)

download http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
$ tar -xzf ta-lib-0.4.0-src.tar.gz
$ cd ta-lib/
$ ./configure --prefix=/usr
$ make
$ sudo make install

Install library talib di Python dan untuk melihat jenis TA yang disediakan:

import talib
talib.get_function_groups()

Sekarang kita coba proses unvr_market yang sudah diambil sebelumnya. Saya coba beberapa indikator.

unvr_market["EMA_20"] = talib.EMA(unvr_market.Close,timeperiod=20)
unvr_market["RSI"] = talib.RSI(unvr_market.Close,timeperiod=20)
unvr_market["MACD"],unvr_market["MACD_SIG"],unvr_market["MACD_HIST"] = talib.MACD(unvr_market.Close)

Sekarang plot dengan matplotlib

import matplotlib.pyplot as plt
import seaborn as sns
sns.set(rc={'figure.figsize':(15,14)})
sns.set_theme(style="whitegrid")
sns.set_color_codes("pastel")
fig, axes = plt.subplots(nrows=3,sharex=True,
gridspec_kw=dict(left=0.1, right=0.9,
bottom=0.1, top=0.95))
fig.suptitle('UNVR', fontsize=20)
unvr_market['Close'].plot(ax=axes[0])
unvr_market['EMA_20'].plot(ax=axes[0])
unvr_market['RSI'].plot(ax=axes[1])
axes[1].axhline(y=70, color='r')
axes[1].axhline(y=30, color='g')
unvr_market['MACD'].plot(ax=axes[2])
unvr_market['MACD_SIG'].plot(ax=axes[2])
for ax in axes:
ax.legend()

Catatan: library finplot dapat digunakan untuk chart terkait finance.

Hasilnya sebagai berikut:

Banyak hal lain yang bisa dieksplorasi dan nanti tentunya menggunakan machine learning 🙂

Pergeseran Topik: Quantitative Trading

Update Okt 2021: Secara tidak terduga, saya jadi pengampu Matakuliah Fintech di Ilkom dan tentu tugas besarnya adalah quantitative trading. Saat ini ada dua mahasiswa bimbingan skripsi saya yang mengambil topik text mining untuk prediksi harga saham dan kami usahakan dibuat papernya. Jadi ternyata tersambung juga dengan akademik 🙂

–end update–

Lebih dari setahun tidak menulis di blog ini 🙂

Bagi yang mencari lanjutan posting sebelumnya tentang pemrosesan teks dengan Keras dapat melihatnya di: https://docs.google.com/document/d/1SQkzjjBdjCNO7cexAAy9s0tGvTsKf1WUJiPrkKPLI-4/edit?usp=sharing

Saya tidak akan melanjutkan lagi posting tersebut.

Saya berkecimpung di bidang NLP sejak membuat paper pertama tahun 2005, pindah ke deep learning tahun 2013. Topik S2 dan S3 saya juga mengenai NLP. Cukup banyak yang saya dapat, baik dari sisi riset maupun proyek-proyek. Tapi ada beberapa hal yang membuat saya tertarik pindah ke bidang baru:

  1. Perkembangan NLP sangat cepat, baik dari sisi riset maupun tools. Saat ini umum terjadi begitu paper di-publish, librarynya muncul di hari yang sama. Tidak perlu repot lagi membuat program, bahkan pretrained model juga sudah ada. Pemula saat ini semakin mudah masuk ke bidang NLP.
  2. Muncul berbagai perusahaan di Indonesia yang fokus pada bidang pemrosesan teks.

Saya sebagai engineer lebih tertarik untuk membuat produk. Tapi saya juga ingin produk yang bisa membiayai sendiri produksi sampai risetnya, bukan sekedar hobi. Oleh karena sejak dua tahun yang lalu saya mulai mencari-cari bidang baru yang relatif belum banyak digarap dan punya potensi besar.

Bidang tersebut adalah seputar investasi saham, khususnya penggunaan algoritma dan machine learning untuk membeli dan menjual saham (quantitative trading). Dengan hanya sekitar 2 juta investor dari 200 juta penduduk, pasar bursa yang belum matang, SDM yang masih terbatas potensi quant trading ini masih terbuka.

Dalam 2 tahun terakhir ini saya mulai coba-coba berinvestasi saham sambil mempelajari domain bisnisnya. Saat ini saya mulai dalam tahap mengumpulkan data dan membaca berbagai buku. Perjalanannya masih panjang, tapi pasti menarik 🙂 Blog dan Twitter saya juga mungkin akan bergeser mengikuti. Tetapi berbeda dengan NLP, saya tidak mengajar matakuliah terkait ini, jadi akan lebih sedikit (atau akan tidak ada) modul tutorial yang saya buat. Data, teknik, fitur yang digunakan pada quantitative trading juga seringkali menjadi rahasia perusahaan sehingga tidak mudah dibagi. Jadi mungkin blog ini akan kembali ke masa sepi 🙂

Pengantar Pemrosesan Teks dengan Keras (Bagian 2: Representasi Teks, Klasifikasi dengan Feedforward NN )

Ini adalah lanjutan dari bagian 1.  Sebaiknya baca bagian1 tersebut jika belum  mengenal tentang konsep tensor, dimensi, shape pada Keras.

Representasi Teks

Teks perlu dikonversi menjadi angka sebelum menjadi input neural network. Keras menyediakan class Tokenizer. Tokenizer ini berfungsi untuk mengkonversi teks menjadi urutan integer indeks kata atau vektor binary, word count atau tf-idf.

Contoh penggunaannya adalah sebagai berikut:

from keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer()
texts = ["Budi makan nasi","Rudi makan nasi, nasi goreng."]
tokenizer.fit_on_texts(texts)

seq = tokenizer.texts_to_sequences(texts)
#kalimat baru
seq1 = tokenizer.texts_to_sequences(["nasi panas sekali"])
print("Index: "+str(tokenizer.word_index))
print("Seq. corpus:"+str(seq))
print("Seq. untuk 'nasi panas sekali':"+str(seq1))

Hasilnya akan seperti ini:

Indeks: {'rudi': 4, 'budi': 3, 'nasi': 1, 'makan': 2, 'goreng': 5}
Seq. corpus':[[3, 2, 1], [4, 2, 1, 1, 5]]
Seq. untuk 'nasi panas sekali':[[1]]
Catatan: "panas" dan "sekali" tidak ada di kosakata jadi tidak ada indeksnya

Dapat dilihat kosakata corpus diubah menjadi indeks (indeks pertama “nasi”, kedua “makan” dan seterusnya). Kalimat kemudian diubah menjadi list urutan dari indeks. List sequence ini kemudian dapat dikonversi menjadi  vektor matriks numpy dengan sequences_to_matrix. Terdapat empat pilihan: tf-idf, binary, count, freq.

Lanjutkan kode sebelumnya untuk mengubah representasi teks berupa urutan indeks menjadi matriks tf-idf sampai frekuensi:

encoded_tfidf = tokenizer.sequences_to_matrix(seq,mode="tfidf")
print("tfidf:")
print(encoded_tfidf)
encoded_binary = tokenizer.sequences_to_matrix(seq,mode="binary")
print("binary:")
print(encoded_binary)
encoded_count = tokenizer.sequences_to_matrix(seq,mode="count")
print("count:")
print(encoded_count)
encoded_freq = tokenizer.sequences_to_matrix(seq,mode="freq")
print("freq:")
print(encoded_freq)

Hasilnya:

tfidf:
[[0.         0.51082562 0.51082562 0.69314718 0.         0.        ]
[0.         0.86490296 0.51082562 0.         0.69314718 0.69314718]]
binary:
[[0. 1. 1. 1. 0. 0.]
[0. 1. 1. 0. 1. 1.]]
count:
[[0. 1. 1. 1. 0. 0.]
[0. 2. 1. 0. 1. 1.]]
freq:
[[0.         0.33333333 0.33333333 0.33333333 0.         0.        ]
[0.         0.4        0.2        0.         0.2        0.2

Hasil sudah berbentuk numpy array. Dapat dilihat padding dilakukan otomatis untuk menyamakan dimensi dengan shape (2,6). Data ini dapat langsung digunakan dalam proses pembuatan model. Alternatif lain adalah menggunakan embedded layer yang akan dibahas dalam posting berikutnya.

Jika proses padding ingin dilakukan secara manual, Keras menyediakan pad_sequences. Contoh penggunaan pad_sequences :

from keras.preprocessing.sequence import pad_sequences
print("Sebelum padding:")
print(seq)
X = pad_sequences(seq)
print("Sesudah padding:")
print(X)
print(X.shape)

Hasilnya:


Sebelum padding:
[[3, 2, 1], [4, 2, 1, 1, 5]]
Sesudah padding:
[[0 0 3 2 1]
[4 2 1 1 5]]
Shape: (2, 5)

Klasifikasi Teks

Dalam bagian ini, akan dilakukan klasifikasi teks menggunakan data SMS spam berbahasa Indonesia dengan arsitektur yang paling sederhana yaitu feed forward NN.

Data dapat didownload di:http://bit.ly/yw_sms_spam_indonesia

Jumlah sample 1143 dan ada tiga kelas dalam dataset ini:

0: sms normal (569 instance)
1: fraud atau penipuan (335 instance)
2: promo (239 instance)

Langkah pertama adalah meload data dari file csv, dapat digunakan library csv. Tambahkan cell berikut.

import csv
nama_file = "C:\\yudiwbs\\data\\sms\\dataset_sms_spam_v1.csv"
data  = []
label = []
with open(nama_file, 'r', encoding='utf-8') as csvfile:
reader = csv.reader(csvfile, delimiter=',', quotechar='"')
next(reader) #skip header
for row in reader:
data.append(row[0])
label.append(row[1])
#test lihat dua data pertama
print(data[:2])
print(label[:2])
#Catatan: parameter encoding dapat dibuang jika muncul error

Alternatif lain adalah menggunakan pandas untuk membaca csv:

import pandas as pd
df = pd.read_csv(nama_file).values
data = df[:, 0]
label = df[:, 1]

Selanjutnya konversi label dari “1”, “2”, “3” menjadi representasi tensor:

from keras.utils import to_categorical
label = to_categorical(label)
print(label.shape)
print(label)

Hasilnya adalah Tensor 2D dengan shape (1143, 3) untuk label, karena ada 1143 instance dengan 3 nilai label yang mungkin (normal, fraud, promo)

Split data menjadi train dan test menggunakan scikit learn, 80% menjadi data train, 20% menjadi data test.

#split jadi train-test
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(data, label,
test_size=0.2,
random_state=123)

Konversi data teks menjadi tf-idf dan tensor. Pastikan Fit hanya dilakukan pada data train untuk mencegah informasi di data test “bocor”.

#konversi teks ke tfidf
from keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer()
#fit hanya berdasarkan data train
tokenizer.fit_on_texts(X_train)
#konversi train
seq_x_train = tokenizer.texts_to_sequences(X_train)
X_enc_train = tokenizer.sequences_to_matrix(seq_x_train,mode="tfidf")
#konversi test
seq_x_test  = tokenizer.texts_to_sequences(X_test)
X_enc_test  = tokenizer.sequences_to_matrix(seq_x_test,mode="tfidf")

print(X_enc_train.shape)
print(X_enc_test.shape)
print(X_enc_train)

Hasilnya adalah tensor 2D dengan shape (914, 4384) untuk data train dan tensor 2D (229, 4384) untuk data test.

Selanjutnya siapkan model dengan menambahkan layer

from keras import models
from keras import layers

_,jum_fitur = X_enc_train.shape
model = models.Sequential()
model.add(layers.Dense(32,activation='relu',input_shape=(jum_fitur,)))
model.add(layers.Dense(4,activation='relu'))
model.add(layers.Dense(3,activation='softmax'))  #karena kelasnya ada 3
model.compile(optimizer="adam",
loss='categorical_crossentropy',
metrics=['accuracy'])

Ada empat layer: layer pertama adalah layer input hasil encode tf-idf sebelumnya: 4384 fitur. Mengapa input_shape tidak menggunakan sample dimension atau sample axis seperti (914, 4384)? karena jumlah samples tidak penting didefinisikan dalam layer input. Dengan teknik mini-batch, sample dapat diproses sedikit demi sedikit, jadi jumlahnya bisa berbeda-beda.

Activation softmax digunakan karena jumlah label ada 3 (normal, fraud dan promo). Jika jumlah label dua (binary classification) maka dapat digunakan activation sigmoid. Setelah layer didefinisikan, maka layer dapat dicompile. Loss categorical_crossentropy dipilih karena terdapat tiga kelas, sedangkan jika untuk binary class dapat digunakan binary_crossentropy.

Kode untuk melakukan training adalah sebagai berikut:

history = model.fit(X_enc_train,y_train,
epochs=3, batch_size=2,
validation_split=0.2)

results = model.evaluate(X_enc_test, y_test)
print("Hasil  [loss,acc] untuk data test:")
print(results)

Satu epoch adalah satu iterasi yang diperlukan untuk memproses seluruh training data. Jika jumlah data training 1000, dan batch_size 20, maka untuk memproses setiap epoch akan diperlukan 1000/20 = 50 steps update bobot. Pada setiap step bobot network akan di-update.

Semakin kecil batch size, semakin kecil memori yang diperlukan dan proses akan konvergen lebih cepat. Kelemahannya, akan memerlukan semakin banyak steps dalam setiap epoch (waktu training semakin lama). Parameter validation_split menentukan persentase data yang akan digunakan untuk data validasi.

Data validasi diambil dari data train dan digunakan untuk meminimalkan nilai loss pada saat training, val_acc dan val_loss adalah metrik untuk data validasi ini. Setelah proses selesai baru kinerja diukur pada data test.

Setelah training selesai, hasilnya adalah sebagai berikut, untuk data test didapat loss 0.33 dan akurasi 0.926 (komputer yang berbeda dapat menghasilkan hasil berbeda):

Proses training dapat memerlukan waktu lama, untuk menyimpan model dan hasil tokenizer ke dalam file gunakan kode berikut:

import pickle
model.save('model_spam_v1.h5')
with open('tokenizer.pickle', 'wb') as handle:
pickle.dump(tokenizer, handle, protocol=pickle.HIGHEST_PROTOCOL)

Berikut adalah kode untuk me-load model, tokenizer dan memprediksi label untuk data baru:

from keras.models import load_model
import pickle
model = load_model('model_spam_v1.h5')
with open('tokenizer.pickle', 'rb') as handle:
tokenizer = pickle.load(handle)

s  = ["Anda mendapat hadiah 100 juta","Beli paket Flash mulai 1GB", "Nanti ketemuan dimana?"]
seq_str = tokenizer.texts_to_sequences(s)
enc_str = tokenizer.sequences_to_matrix(seq_str,mode="tfidf")
enc_str.shape
pred = model.predict_classes(enc_str)
print("Prediksi kelas string ' {} ' adalah {}".format(s,pred))

Hasilnya:
Prediksi kelas string ‘ [‘Anda mendapat hadiah 100 juta, ‘Beli paket Flash mulai 1GB’, ‘Nanti ketemuan dimana?’] ‘ adalah [1 2 0]

Bersambung..  (word embedding, RNN)

Update Juni 2020: artikel ini tidak akan dibuat kelanjutannya, tapi materi lengkap tentang ini bisa dilihat di: https://docs.google.com/document/d/1SQkzjjBdjCNO7cexAAy9s0tGvTsKf1WUJiPrkKPLI-4/edit?usp=sharing

Pengantar Pemrosesan Teks dengan Keras (Bagian 1: Tensor, Batch, Layer dan Learning)

Deep learning saat ini sudah umum digunakan dalam berbagai task NLP.  Materi posting ini saya ambil dari buku Chollet “Deep Learning with Python” dan berbagai sumber lain.  Perlu dibagi menjadi beberapa bagian karena berdasarkan pengalaman saya jika langsung loncat ke source code tanpa paham dasar-dasarnya  nanti malah jadi  bingung sendiri. Posting ini masih membahas Keras secara umum.

Keras adalah library yang ditujukan untuk memudahkan pembuatan model NN (Neural Network). Keras menyediakan fasilitas untuk membuat berbagai arsitektur NN seperti  convolutional, recurrent, multi input-multi output, layer sharing dan model sharing.  Keras menggunakan backend Tensorflow atau CNTK (Microsoft Cognitive Toolkit). Lisensi keras adalah MIT yang longgar sehingga mudah digunakan untuk kepentingan komersial.

Instalasi Keras sangat mudah: pip install tensorflow lalu pip install keras

Urutan umum pembuatan NN di Keras adalah:

  1. Mendefinisikan data training  berupa input tensor dan target tensor.
  2. Mendefinisikan layer yang akan memetakan antara input ke target.
  3. Konfigurasi proses learning dengan memilih loss function, optimizer dan metric yang harus dimonitor.
  4. Lakukan training dengan memanggil method fit()

Konsep dasar yang penting dalam Keras adalah Tensor.  Tensor adalah struktur data berupa matrix atau multidimensional array yang menjadi dasar penyusun NN. Tensorflow dan library deep NN lainnya adalah library yang dapat memproses secara efisien komputasi pada tensor.

Ukuran dimensi yang umum pada tensor adalah 0D sampai 5D, contoh:

  1. Tensor 0D yang berisi satu nilai;
  2. Tensor 1D, satu dimensi atau vektor. Misalnya  x = np.array([10,12,13,14]).  Variabel x ini karena memiliki empat nilai disebut 4-dimensional vector tetapi tetap tensor 1 dimensi karena hanya memiliki satu axis. Jadi perlu dibedakan antara 4D vector dan 4D tensor.
  3. Tensor 2D, matrix.  Contoh:  x = np.array( [[10,12,13,14], [11,21,32,41]] ).  Variabel x ini dapat dilihat sebagai dua instance yang memiliki 4 fitur. Tensor ini cocok digunakan untuk merepresentasikan data berbentuk tabel: axisnya adalah (instance, features).
  4. Tensor 3D. Misalnya untuk merepresentasikan time series. Axisnya: (instance, timesteps, fitur).
  5. Tensor 4D. Cocok untuk merepresentasikan image, axisnya: (instance, tinggi, lebar, warna).
  6. Tensor 5D. Video sering direpresentasikan dengan tensor ini dengan axis: (samples, frames, tinggi, lebar, warna)

Dokumen teks dapat direpresentasikan dengan frekuensi kemunculan kata atau tf-idf. Dengan asumsi jumlah kosakata 20000, maka tiap dokumen dapat direpresentasikan dengan vektor 20000 elemen. Jika terdapat 500 dokumen, data dapat direpresentasikan dengan tensor 2D dengan axis (instance, vektor tf-idf) atau shape  (500, 20000). Mengenai shape akan dijelaskan nanti.

Contoh lain adalah tweet, asumsikan tweet akan direpresentasikan di level karakter. Panjang setiap tweet maksimal adalah 280 karakter dengan jumlah karakter yang mungkin adalah 128 (a-z, A-Z, 0-9 dst). Setiap karakter dapat di-encode sebagai binary vector berukuran 128, contohnya karakter “a” menjadi [1,0,0,0 ….0], karakter “b” [0,1,0,0… 0] dan seterusnya. Jadi setiap tweet dapat di-encode menjadi tensor 2D (280, 128) dengan isi 0 dan 1. Jika terdapat 1 juta tweet, maka akan disimpan dalam tensor 3D denga axis (instance, jumlah_karakter, encode_karakter)  atau shape (1000000, 280, 128).

Tensor didefinisikan oleh tiga atribut,  jumlah axis, shape dan tipe datanya.

  1. Axis sudah dibahas sebelumnya, tensor 3D akan memiliki jumlah axis (rank) =3, tensor 2D rank-nya = 2 dst.
  2. Tipe data (dtype) yang digunakan tensor: float32, uint9, float64 dan char.
  3. Shape memperlihatkan dimensi yang dimiliki untuk setiap axis. Contoh untuk  tensor 3D berikut ini, shapenya adalah (2,3,4). Axis nol terdiri atas 2 dimensi, axis ke-1 tiga dimensi dan axis ke-3  4 dimensi:

x = np.array(
[[[10,12,13,11],
[11,21,32,15],
[30,21,31,22]],
[[10,12,13,4],
[11,31,52,5],
[21,51,12,9]]]
)
print(x.shape) # hasilnya (2,3,4)

Contoh lain untuk tensor 2D berikut,  shape adalah (2,4)
x = np.array([[10,12,13,14],[11,21,32,41]])

Vector dengan elemen tunggal ini akan memiliki shape (4,)
x = np.array([10,12,13,14])

Scalar akan memiliki shape ()

Batch: Axis & Dimension

Umumnya axis yang pertama (axis ke-0) disebut sample axis atau sample dimension. Sebagai contoh untuk pengenalan karakter dari gambar yang terdiri atas 1000 gambar dengan 30×30 pixel grayscale, akan menggunakan tensor 3D dengan shape (1000, 30, 30) yang berisi nilai 0..255 (grayscale). Axis ke-0 (1000) adalah jumlah instances atau samples. Pada tensor 2D dengan shape (10000, 20) artinya ada 10000 samples dengan 20 fitur. Dapat dianggap  shape adalah (samples, feature).

Model DL umumnya tidak memproses data secara sekaligus tetapi dibagi-bagi menjadi batch yang lebih kecil. Itu sebabnya axis 0 sering juga disebut batch axis atau batch dimension.

Layer

Layer adalah bagian yang menerima input satu atau lebih tensor dan mengeluarkan output satu atau lebih tensor. Ada layer yang stateless, tetapi umumnya layer memiliki state yaitu bobotnya.

Terdapat berbagai jenis layer untuk berbagai jenis pemrosesan data. Keras menyediakan layer standar yang umum digunakan dalam deep learning. Misalnya dense connected layer (kelas Dense di Keras), recurrent layer (kelas LSTM) dan convolutional layer (kelas Conv2D). Membangun deep NN di Keras adalah menyusun berbagai layer seperti membangun mainan LEGO.

Setiap layer menerima input tensor dan mengeluarkan output tensor dalam ukuran (shape) yang sudah ditentukan. Ini disebut layer compatibility.

Sebagai contoh, code berikut membuat model dengan dua layer berukuran 32 neuron dan menerima 500 fitur pada layer input. Layer kedua adalah layer output.

from keras import models
from keras import layers

#definisikan model sebagai urutan layer
model = models.Sequential()
#model menerima input dengan jumlah fitur 500 dan output 32
#jumlah instance tidak perlu dituliskan dalam input_shape
model.add(layers.Dense(32, input_shape=(500,)))
#input layer ini otomatis 32 (output layer sebelumnya)
model.add(layers.Dense(32))

Sebagai contoh, code berikut membuat model dengan dua layer berukuran 32 neuron dan menerima 500 fitur pada layer input. Layer kedua adalah layer output.

Learning

Pada Keras, tahapan learning dikonfigurasi di bagian kompilasi. Konfigurasi yang perlu didefinisikan adalah optimizer dan loss function yang akan digunakan model dan metric yang akan dipantau  saat training. Loss merupakan jarak antara nilai target dan prediksi untuk setiap sample, sedangkan metric mengukur kinerja keseluruhan model.

Loss function mengukur kinerja NN terhadap data training yang  digunakan untuk mengarahkan atau memperbaiki network ke arah yang tepat. Keras menyediakan beberapa loss function standar yang dapat dipilih sesuai task, misalnya untuk klasifikasi biner, dapat digunakan binary cross entropy; untuk klasifikasi multi kelas dapat digunakan categorical cross entropy; mean-squared error untuk regresi dan connectionist temporal classification (CTC) untuk sequence labeling.

Optimizer adalah mekanisme yang digunakan  untuk mengupdate bobot jaringan berdasarkan loss function. Berbagai jenis class optimizer di Keras yang dapat digunakan adalah RMSprop, Adagrad, Adadelta, Adam, Adamax, Nadam.

Contoh proses compile dan training dapat dilihat pada kode berikut

from keras import optimizers
model.compile(optimizer=optimizers.RMSprop(lr=0.001),loss='mse',metrics=['accuracy'])

Setelah itu, input dan target diberikan pada method fit() seperti pada Scikit-Learn

model.fit(input_tensor, target_tensor, batch_size=128, epochs=10)

Contoh lebih rinci dapat dilihat pada posting berikutnya, klasifikasi teks [bersambung].

Posting lanjutan: bagian 2, representasi teks dan klasifikasi teks dengan feed neural network.

Pos Tagger dan Dependency Parser dengan StanfordNLP Python

Sebelumnya saya sudah buat tulisan tentang pos tagger & dependency parser Bahasa Indonesia dengan lib CRFTagger, UUParser dan Syntaxnet.  Hanya Syntaxnet yang menyediakan pretrained model. Saat ini ada satu lagi library yang menyediakan pretrained model untuk Bahasa Indonesia: StanfordNLP (https://github.com/stanfordnlp/stanfordnlp).

StanfordNLP sudah ada cukup lama, tapi awalnya menggunakan Java dan lisensinya GPL (kita perlu bayar lisensi terpisah jika buat sistem yang tidak open source).  Sejalan dengan dominasi Python dan lisensi yang lebih longgar seperti MIT dan Apache, maka dikembangkan lib baru, dan StanfordNLP yang lama diubah namanya menjadi CoreNLP.

StanfordNLP  ini sudah native python (bisa diinstall dengan pip install stanfordnlp),  menggunakan deep learning (pytorch) dan sudah menyediakan pretrained model untuk bahasa Indonesia.

Saat saya coba, ternyata lib ini memerlukan Python 3.6 dan 3.7.   Saya install saja versi yang terakhir, yaitu versi  3.7.2, sayangnya untuk versi ini Python harus dicompile manual. Langkah-langkahnya saya tiru dari: https://tecadmin.net/install-python-3-7-on-ubuntu-linuxmint/  tapi dengan modifikasi sedikit karena di artikel itu kurang dua library yaitu libffi dan liblzma (saya menggunakan Ubuntu 16).

sudo apt-get install build-essential checkinstall
sudo apt-get install libreadline-gplv2-dev libncursesw5-dev libssl-dev \
    libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev libffi-dev liblzma-dev
cd /usr/src
sudo wget https://www.python.org/ftp/python/3.7.2/Python-3.7.2.tgz
sudo tar xzf Python-3.7.2.tgz
cd Python-3.7.2
sudo ./configure --enable-optimizations
sudo make altinstall

Catatan: interpreter hasil compile  disimpan di /usr/local/bin/python3.7

Update Juni 19: Untuk Windows dengan Anaconda, masalahnya ada di instalasi torch (saya menggunakan PyCharm). Buka conda prompt, aktifkan virtual env target, pergi  ke website  Pytorch untuk dapat instruksi instalasi.

Setelah pip install stanfordnlp, kode berikut adalah contoh penggunaannya. HATI-HATI, perlu satu jam untuk menjalankan kode ini, entah karena harus menggunakan GPU atau ada optimasi yang belum saya lakukan. Update: running berikutnya hanya perlu 1-2 menit, tetap lebih lama dibandingkan lib lain.

import stanfordnlp
stanfordnlp.download('id')   # download 1.4GB model
nlp = stanfordnlp.Pipeline(lang="id",use_gpu=False)
doc = nlp("Budi makan nasi enak sekali.")
print("token:")
doc.sentences[0].print_tokens()
print("dependency parse:")
doc.sentences[0].print_dependencies()

Hasilnya adalah sebagai berikut:


token:
Token index=1;words=[Word index=1;text=budi;lemma=budi;upos=NOUN;xpos=NSD;feats=Number=Sing;governor=2;dependency_relation=nsubj]
Token index=2;words=[Word index=2;text=makan;lemma=makan;upos=VERB;xpos=VSA;feats=Number=Sing|Voice=Act;governor=0;dependency_relation=root]
Token index=3;words=[Word index=3;text=nasi;lemma=nasi;upos=NOUN;xpos=NSD;feats=Number=Sing;governor=2;dependency_relation=obj]
Token index=4;words=[Word index=4;text=enak;lemma=enak;upos=ADJ;xpos=ASP;feats=Degree=Pos|Number=Sing;governor=3;dependency_relation=amod]
Token index=5;words=[Word index=5;text=sekali;lemma=sekali;upos=ADV;xpos=D--;feats=_;governor=2;dependency_relation=punct]

dependency parse:
('Budi', '2', 'nsubj')
('makan', '0', 'root')
('nasi', '2', 'obj')
('enak', '3', 'amod')
('sekali', '4','advmod')
('.', '2', 'punct') 

Belajar Investasi Saham

Video tutorial saya untuk pemula mendaftar ke sekuritas: https://youtu.be/ekSFxv85e3c

Video cara memilih saham (bagian 1): https://youtu.be/30EGZ6oYP04

Video cara memilih saham (bagian 2) tentang likuiditas, membahas DER, cash ratio, quick ratio, current ratio dan ebitda to interest coverage: https://youtu.be/M71dopbT7o0

Update Nov 2021:
Sudah lama tidak update, rincian catatan ada di twitter saya di @yudiwbs_saham.
Kondisi portfolio saham saya di bulan Nov 2021:

Pada Maret sd Nov Saya tambah ACES dan SMSM. Kinerja keduanya memang turun saat pandemi, tapi perkiraan saya saat ekonomi pulih, mereka akan pulih dengan cepat. ACES perlu terus dimonitor karena persaingan yang ketat. Di bulan Maret-Mei saya beli ICBP secara bertahap (avg down) sampai jadi emiten terbesar di portfolio tapi kemudian dijual di bulan Oktober karena aspek likuiditas yang membuat saya tidak nyaman (hutang yang besar, interest coverage ratio < 5 ). Pada penjualan ini saya untung 7% (masih lumayan). Setelah 3 tahun berinvestasi saham, saya belum sekalipun jual rugi. Jadi sebenarya kalau kita mau belajar dan tidak serakah, investasi saham sebenarnya bisa memiliki resiko yang rendah. Sebagian dana hasil penjualan ICBP saya belikan CLEO. Saya suka CLEO karena pertumbuhan pendapatan dan peningkatan net profit margin. Likuiditas CLEO juga bagus.

Update Jan 2021:
ACES bisa saya beli kembali di Nov 2020, saat turun dibawah harga jual saya sebelumnya. Beli lumayan banyak sekarang. Saya juga mencoba investasi sukuk (ST007) dengan return 5.5% per tahun, untuk tenor 2 thn. Ternyata lumayan lama prosesnya. Pada bulan Nov saya juga menjual TLKM dengan keuntungan 5.5% (4 bulan). TLKM dijual karena kinerjanya turun (padahal harusnya di masa pandemi dia paling diuntungkan) dan hutangnya naik. Setelah saya jual, saham ini naik terus hehe karena membeli saham GOJEK. PWON sebenarnya sempat positif tapi kemudian turun sehingga tidak sempat terjual.

Kondisi portfolio saham saya akhir tahun 2020, posisi cash cukup besar:

Update Sept 2020:
Menjual ACES sepertinya keputusan yang salah, ternyata ACES secara fundamental tidak terlalu terpukul dan harga sahamnya malah naik terus. Pelajaran bagi saya bahwa jangan jual emiten bagus dengan berharap akan beli lagi di kemudian hari setelah harganya turun. Jual hanya kalau sangat kemahalan atau ada fundamental yang berubah. Terkait itu, saya jual ICBP karena menurut saya harga akuisisinya memang terlalu mahal. Saya jual dengan profit 7%, kecil secara persentase tapi lumayan terasa karena proporsi ICBP di portofolio saya cukup besar. Ini kelebihan model portofolio yang lebih terkonsentrasi. Pembagian dividen juga jadi lebih terasa (SMSM membagikan 4 kali setahun). ULTJ juga saya jual (profit 4.3%) karena laporan keuangannya yang tidak tertib (di situs IDX dan ultrajaya telat muncul). DVLA juga saya jual (profit 5.3%) karena pertumbuhan revenue selama 5 tahun terakhir turun dan tidak likuid. Saya mulai mencoba trading dengan beli TLKM karena harganya sedang murah, jika sudah mencapai target price akan saya lepas (bukan untuk dipegang dalam jangka panjang). Saat ini portofolio hanya ada 4 saham: TLKM, SIDO, SMSM dan PWON. Saat ini hanya SIDO dan SMSM yang positif. Posisi cash di RDI saat ini mencapai 35%, karena saya lihat bursa di US naik terus dan kondisi covid di Indonesia belum membaik, lebih baik punya cash untuk siap-siap kalau terjadi crash seperti bulan Maret 2020. Waktu Maret itu saya sempat beli tapi tidak banyak.

Update Mei 2020:
Covid-19  berdampak besar pada bursa. Sempat crash di akhir maret (porto saya turun sampai -30%) sekarang sudah lebih stabil (porto -15%). Emiten yang saya miliki yang  turun  paling  dalam adalah PWON,  kelebihannya memiliki pendapat berulang melalui mall dan hotel malah menjadi menjadi beban.  Saya lepas DVLA (untung 5.3%, Jan 2019) karena kurang likuid dan lepas ACES (6.8%, Jan 2020) karena termasuk emiten yang akan kena dampak besar. ACES mungkin akan saya beli lagi di Q2 atau Q3 2020, karena ini sebenarnya emiten bagus (mudah-mudahan saat itu harganya akan rendah).  Saat ini hanya SIDO yang positif di porto saya, lainnya ULTJ, PWON, SMSM, ICBP negatif.

Update Des 2019:
Setahun sudah saya investasi saham. Sisi bagusnya, sampai saat ini belum pernah rugi saat menjual walaupun ada beberapa yang saya anggap salah beli.  Saham yang saya anggap salah beli tapi (untungnya) berhasil dijual dan tetap untung: HRTA (cashflow negatif), MTLA (pertumbuhan lambat), TPMA (lambat,  perlu pinjaman besar).  ADRO  dan CPIN (volatil dan rentan terhadap kebijakan pemerintah). Ini dijual dengan untung 16%, 10%, 4%, bahkan ada yang hanya 0.4%.   Disisi lain, saham yang bagus tapi saya jual karena menurut saya harganya terlalu tinggi dengan prospek ke depan relatif lambat pertumbuhannya: EKAD. EKAD ini bisa dijual dengan untung 31% (ini profit yang paling besar).  Total keuntungan  (realized profit)  15.5%.  Lumayanlah untuk saya yang baru setahun belajar.

Untuk unrealized profit (untung/rugi di atas kertas) portofolio saya sekarang 1.38%.  Ini disebabkan karena saya average down SMSM dan PWON. IMO dua emiten  punya potensi besar tapi harganya sedang turun. Portofolio saya yang lain: SIDO, ULTJ dan DVLA. Tadinya  10 emiten mengerucut jadi 5.

Update Feb 2019:
Tiga bulan saya sudah mulai masuk pasar saham. Saya menggunakan dollar cost averaging, jadi tiap awal bulan ada jumlah fix yang bisa digunakan untuk membeli saham. Tapi saya belanjakannya tidak dipaksakan satu hari tertentu, bisa 1-2 minggu menunggu saat yang tepat. Sangat bagus mulai dari jumlah kecil untuk belajar,  karena ternyata lumayan tegang saat harga saham naik turun. Padahal idealnya kinerja itu diukur 1-2 tahun setelah saham dibeli, tidak perlu cek harga tiap hari.  Lalu saat salah analisis (karena belum tahu) dan keburu beli, ada keinginan kuat untuk menjual. Jumlah investasi kecil membantu untuk lebih menenangkan hati. Nanti saat sudah terbiasa, jumlahnya bisa dinaikkan.

Saya bergabung dengan beberapa group telegram untuk mengetahui dinamika para pemain pasar saham.  Rekomendasi saham dari group justru saya jauhi. Logikanya kalau saya jadi admin group itu, urutan aksi saya adalah: saya beli saat murah,   umumkan di group berbayar baru terakhir umumkan di group gratis. Ini akan berakibat saham naik.  Lalu admin akan jual untuk ambil keuntungan.   Jadi sudah terlambat kalau kita ikuti rekomendasi group. Lagipula umumnya  fundamental saham yang direkomendasi memang tidak terlalu bagus. Sama dengan rekomendasi para “analis” di media , banyak rekomendasi ajaib, sudah jelek fundamentalnya, mahal pula.

Saham yang saya hindari (masalah personal saja, tiap orang punya selera dan prinsip berbeda):

  1. BUMN. Walaupun sering mendapat perlakuan khusus dari pemerintah sehingga bisa menguasai pasar, tapi akibatnya sering tidak efisien, beresiko korupsi, dan tidak berhati-hati (misal urusan hutang). Masalah lain: pemerintah juga dapat ikut campur urusan internal perusahaan dan eksekutif sulit ambil resiko bisnis karena takut dipidana.
  2. Finansial, selain riba, saya juga tidak paham proses bisnisnya. Jadi walaupun bank syariah tetap saya hindari.
  3. Rumah sakit, rokok dan minuman keras.

END update

—-

Update Jan 2019:
Perlu berhati-hati, terlepas sentimen negatif dan prediksi kuat terjadinya resesi 2019-2020, pasar saham Indonesia sepertinya tidak terpengaruh. Investor (retail) kita sepertinya masih bersemangat sekali. Ini justru yang buat saya khawatir.  Sekarang sulit cari saham yang  bagus (bagi saya) tapi dengan harga yang murah. Lebih baik jangan dipaksakan beli.
–end update–

———

Posting yang agak berbeda dengan biasa 🙂  tapi sebenarnya nanti berhubungan juga dengan NLP dan  machine learning.  Posting ini akan diupdate sejalan dengan perjalanan saya belajar tentang investasi di pasar saham, jadi mungkin posting ini nantinya akan sangat panjang.  Posting ini adalah sudut pandang saya yang awam.

Saya sudah lama tertarik untuk berinvestasi di pasar saham, tapi baru bisa mulai sekarang. Masalahnya, investasi saham walaupun dapat menghasilkan return tinggi, tetapi resikonya juga tinggi. Dalam kasus terburuk uang bisa berkurang atau bahkan hilang.  Itu sebabnya uang untuk investasi saham menunggu  antrian yang urutannya sebagai berikut: tabungan, dana pendidikan anak, deposito syariah, dana pensiun (diluar taspen PNS), investasi yang relatif aman baru terakhir saham. Ini yang saya lihat sering dilupakan orang, saya pernah baca pertanyaan  “Saya punya banyak hutang, saya tertarik bermain saham, bagaimana caranya?”, gawat kalau seperti itu.

Tadinya saya berminat untuk memulai dari reksadana dulu, tetapi melihat fee yang lumayan lalu banyak analis  yang rekomendasinya aneh, sepertinya lebih baik saya pilih sendiri saham yang mau saya koleksi. Rugi atau untung semua tanggung jawab saya 🙂

Banyak yang bingung mulai dari mana? untuk langkah pertama menurut saya adalah membaca sebanyak mungkin informasi. Hati-hati banyak informasi di internet, khususnya video Youtube yang hanya menyampaikan bahwa   investasi saham itu bisa untung besar tapi tidak  membahas kemungkinan rugi (yang bisa besar juga) dan tidak membahas bahwa tingkat kesulitannya cukup tinggi.  Banyak yang terlalu menyederhanakan seperti “Beli saham bluechip saja!”.

Mendapatkan keuntungan dari saham itu  bisa dengan dua cara: harga sahamnya naik (contoh beli 100/share lalu sebulan kemudian menjadi 300/share) dan melalui dividen (bagi hasil keuntungan perusahaan). Masalah utamanya adalah memilih saham yang mana dan kapan.

Saya lihat ada dua pendekatan: pendekatan pertama adalah berdagang saham jangka pendek.   Saat melihat potensi harga  naik, maka saham akan dibeli dan pada saat titik yang dianggap menguntungkan maka saham tersebut dijual, untung dapat diperoleh dari selisihnya. Ini bisa berlangsung dalam hitungan jam atau hari. Pendekatan ini mirip berselancar (mengikuti harga yang naik). Masalahnya sama dengan berselancar, salah pemilihan waktu sedikit saja, maka akan kehilangan ombak. Informasi yang digunakan umumnya hanya sejarah harga saham dan volume penjualan. Teknik yang perlu dipelajari untuk pendekatan ini disebut analisis teknikal.

Pendekatan kedua adalah jangka panjang (6 bulan sampai tahunan).  Pada pendekatan ini perlu dicari perusahaan yang bagus tapi sahamnya relatif dihargai murah. Saham milik perusahaan yang fundamentalnya bagus, walaupun saat ini dihargai murah,  nantinya akan naik juga dan kenaikannya bisa besar (berkali-kali lipat) sejalan dengan semakin besarnya perusahaan. Keuntungan juga bisa didapat dari dividen.  Masalahnya bagaimana menentukan perusahaan ini bagus? jangan-jangan murah karena memang mau bangkrut.  Informasi yang digunakan mulai dari laporan keuangan (tahunan, triwulan), analisis kemampuan manajemen, analisis sektor yang terkait dan sebagainya.  Pendekatan ini disebut value investing dan teknik yang perlu dipelajari adalah analisis fundamental.

Di Indonesia, yang saya lihat mayoritas yang digunakan adalah pendekatan pertama (trading).  Menurut saya karena alasan berikut:  1) lebih sederhana karena variabel yang digunakan lebih sedikit dan kuantitatif.  2) Perusahaan sekuritas mendapat fee jika ada transaksi, jadi mereka mendorong pelanggan melakukan transaksi sebanyak-banyaknya 3) lebih seru, seperti menonton film aksi atau balapan, ada keasyikan sendiri menyaksikan harga naik (atau turun) menit demi menit sebelum memutuskan beli atau jual. Walaupun saat ini sudah ada  mekanisme otomatis beli dan jual berdasarkan kriteria tertentu, tetapi tetap saja seru.

Saya pribadi lebih suka pendekatan kedua (value investing), karena tujuannya memang untuk jangka panjang (20 tahunan). Saya suka membaca, mencari dan menganalisis data dan  informasi. Wajar karena berkecimpung di bidang machine learning dan datamining 🙂 Membaca laporan tahunan adalah kegiatan menarik bagi saya. Saya juga tidak terlalu suka lagi nonton pertandingan dan bermain game (mungkin faktor umur?) jadi memonitor pergerakan harga saham jam demi jam (apalagi menit demi menit) bukan lagi hal yang terlalu menyenangkan. Lebih baik waktunya digunakan untuk hal yang lain. Sebagai bonus, biaya transaksi untuk sekuritas juga akan lebih murah.

Menurut saya memilih pendekatan trading atau value investing (atau gabungan) tergantung dengan minat, bakat dan tujuan tiap individunya. Yang penting bekali diri dengan ilmu sebelum masuk.  Tanpa ilmu,  sama saja dengan judi.  Banyak yang malas belajar dan ingin jalan pintas: “Udah, pokoknya apa saham yang harus saya beli besok?”, “Saham X bagus nggak gan?”.  Kadang bergidik,   mau beli barang 500 ribu saja kita  harus pikir-pikir dan banding-bandingkan dulu,  ini  jutaan rupiah dipertaruhkan  seperti dengan lempar dadu saja.

Karena saya memilih value investing, tentu saya lebih mendalami materi-materi yang terkait analisis fundamental. Video tutorial yang bagus adalah: https://www.youtube.com/watch?v=KfDB9e_cO4k&list=PLECECA66C0CE68B1E

Untuk buku:

  1. “Stock Market Investing for Beginner”, Tycho Press.
  2. “Fundamental Analysis for dummies”, Krants
  3.  Peter Lynch (ada tiga buku, dengan urutan baca “One Up On Wall Street”, “Learn to Earn”,  “Beating the Street”).  Kalau tidak sempat cukup baca yang “One Up On Wall Street” saja, dua yang lain hanya pelengkap. Peter Lynch ini menarik karena dia termasuk fund manager yang fokus pada pemilihan saham dan sukses (rata-rata return 29.2% per tahun selama 13 tahun). Tapi perlu hati-hati untuk mengikuti petunjuk dia. Gaya investasinya  lebih agresif dan dia kerja gila-gilaan (mulai dari jam 6 pagi sampai malam termasuk sabtu). Aktivitasnya termasuk mendatangi secara langsung atau menelepon calon emiten yang dia incar, jadi salah satu kunci keberhasilannya adalah punya informasi yang mendalam. Mungkin aktivitas seperti ini yang bisa diganti dengan datamining? Saya paling hanya googling saja untuk informasi, tapi untuk sampai menelepon apalagi mendatangi perusahaan ya berat.
  4. “The Intelligent Investor”, Graham. Ini yang paling berat, sebaiknya dibaca terakhir.
  5. Update Jan 2019. Karena tertarik dengan video Youtubenya, saya beli buku Phil Town (Rule #1). Buku ini lebih pragmatis dengan aturan yang lebih jelas. Tapi perlu hati-hati karena  Phill Town sendiri tidak memperlihatkan kinerja portofolionya. Saya lebih cocok dengan Lynch yang lebih fleksibel, tapi ada beberapa hal yang bermanfaat juga. Intinya aturan dari buku ini: hanya beli saham dengan kondisi: 1) ROIC >10% (kenapa bukan ROE ya?)  2) Equity growth >10%  3) EPS growth >10%   4) Sales growth >10%   5) Free cashflow >10%.  Setelah itu hitung nilai intrinsik saham dengan cara hitung nilai 10 tahun ke depan berdasarkan PER, EPS growth, mundurkan ke harga saat ini berdasarkan target pertumbuhan 15% (Indonesia mungkin  harus lebih tinggi),  ini yang jadi harga intrinsik.  Lalu hanya beli yang harganya 50% dari harga intrinsik (margin of safety).

Perlu juga baca yang terkait pasar Indonesia, sayang judulnya agak  berlebihan:

  1. “Value Investing beat the market in 5 minutes”, Teguh Hidayat.  Lebih untuk pengantar, menggunakan bahasa non formal, cocok untuk orang muda.
  2. “Investasi Saham ala Fundamentalis Dunia”, Ryan Filbert, William Prasetya. Sisi dasar-dasar akuntasi, untuk membaca laporan keuangan.

Lumayan capek ya 🙂  Tapi saat investasi kita bisa hilang jadi nol rupiah menurut saya wajar perlu persiapan ekstra, itupun tetap tidak menjamin, tapi setidaknya jauh mengurangi resiko.

Selanjutnya bisa dimulai melihat-lihat laporan keuangan perusahaan incaran. Semua laporan emiten bisa dilihat di: https://www.idx.co.id/perusahaan-tercatat/laporan-keuangan-dan-tahunan/   Bisa juga mencari saham yang sesuai kritera yang diinginkan melalui stock screener: https://www.indopremier.com/ipotgo/marketanalysis.php?page=stockscreener

Untuk informasi tentang saham, berikut dua situs bagus dan ada app Android-nya juga:

  1. www.indopremier.com/ipotgo   Tapi hati-hati, tidak memperhitungkan stock split (contoh EPS ULTJ yang terlihat jadi drop drastis dari 16-17).
  2. rti.co.id (klik free trial)

Setelah punya gambaran saham yang mau dibeli, pilihan ini dapat diujicoba (disimulasikan) secara virtual pada situs stockbit.com sekalian belajar mekanisme beli-jual. Selanjutnya kita sudah siap untuk terjun membeli saham sesungguhnya.

Memilih dan Mendaftar pada Perusahaan Sekuritas

Transaksi saham harus dilakukan melalui perusahaan sekuritas sebagai perantara.  Mereka mendapat fee setiap transaksi  (untuk BNI sekuritas, fee-nya adalah 0.15% untuk beli dan 0.25% untuk jual). Ada banyak perusahaan sekuritas, tapi karena saya sudah punya rekening BNI dan ingin perusahaan sekuritas yang masih ada sampai 20 tahun lagi maka saya memilih BNI Sekuritas. Saya tanya-tanya via Twitter cepat dijawab (marketing), tapi untuk yang teknis, harus tanya via WA, dan responnya lambat (bisa 1jam) plus kurang ramah. Tapi yang penting dijawab sih bagi saya.

Saya buka rekening syariah, proses pendaftarannya: kita ke situs BNI sekuritas, isi berbagai formulir,  download formulir, print, beri paraf, beri materai, tandatangan lalu kirimkan via pos. Tunggu sampai 5 hari kerja. Total waktu yang diperlukan kira-kira 2 minggu. Nanti otomatis akan dibuatkan dua  RDI (Rekening Dana Investasi),  satu untuk syariah, satunya untuk yang biasa. Rekening ini yang digunakan untuk menampung dana untuk membeli, menjual atau dividen saham. Tidak ada info rekening syariah yang mana, tapi saran saya isi saja dua rekening itu masing-masing dengan 1 jt (nilai minimum). Walaupun punya dana banyak, sebaiknya mulai dari kecil dulu.

Ternyata yang disebut rekening syariah itu hanya sekedar membatasi saham apa yang bisa dibeli (misal saham bank, saham minuman keras tidak boleh dibeli). Jadi rekening yang non syariah saya gunakan juga,  yang penting jangan beli saham non syariah.  Bisa saja satu rekening untuk investasi jangka pendek satunya untuk jangka panjang. Setelah digunakan tidak perlu ada uang mengendap pada  rekening itu (semuanya bisa dibelikan saham). Saat membeli, nanti akan ada isian rekening mana yang akan digunakan, yang syariah ujungnya ada “S”.  Nanti akan dapat email dari sekuritas dan kita bisa tahu rekening syariah yang mana.

Kita juga akan diberikan user dan password untuk masuk ke aplikasi trading milik BNI (esmart) dan pin untuk bertransaksi. Saya tidak terlalu peduli dengan fitur-fitur trading, biasanya saya hanya buka sebentar dan langsung beli dengan harga yang ada.  Setelah transaksi, tidak otomatis uang di rekening didebet, ini dilakukan dua hari kemudian. Santai saja, BNI sekuritas akan mengirimkan email tentang transaksi, dividen dsb.

Selanjutnya tinggal memonitor fundamental emiten yang sahamnya kita miliki atau calon yang menarik untuk dikoleksi. Selama fundamentalnya bagus, saham jangan dijual. Perlu pembahasan terpisah untuk itu, dan saya masih belajar 🙂

Mengenai penerapan machine learning khususnya NLP pada pasar saham: saya belum mendalami, tapi umumnya ditujukan untuk fast trading (misalnya saat ada berita negatif tentang sektor X, maka semua saham terkait X dijual dalam waktu sekian milidetik). Istilah yang digunakan adalah algorithmic trading dan orang yang membuat algo-nya disebut  quant.  Salah satu tutorial  tentang algo trading ini dapat dilihat di https://www.youtube.com/watch?v=GlV_QO5B2eU  Sayangnya untuk pasar saham Indonesia sepertinya belum ada platform untuk backtest.

Pemanfaatan NLP untuk analisis fundamental sudah ada, tapi sepertinya tidak terlalu banyak.  Saya pernah baca paper yang memproses  laporan tahunan untuk memprediksi kinerja emiten. Menarik, karena memang setelah saya baca laporan tahunan,  dapat dilihat dirut yang isi tulisannya hanya normatif biasa-biasa saja, tapi ada juga yang kelihatan menguasai bidangnya dan bersemangat.  Bagusnya semua laporan tahunan ada versi bahasa Inggris-nya, jadi kita bisa menggunakan resources bahasa Inggris.  Mungkin ini yang paling dekat yang akan saya coba.