Advanced Platform Research Python Data Analysis & Strategi Backtest

Penulis:Ninabadass, Dibuat: 2022-04-13 09:12:47, Diperbarui: 2022-04-28 11:06:13

Penelitian Platform Lanjutan Analisis Data Python & Strategi Backtest.ipynb

Penelitian Platform Lanjutan

FMZ memiliki built-in jupyter notebook untuk membantu pengguna membiasakan diri dengan API platform dan melakukan penelitian strategi, dan mendukung lingkungan pembelajaran Python3 C++11/17 dan Javascript. Notebook+Python adalah alat yang sangat kuat, yang hampir tidak bisa dipungkiri untuk analisis data dan penelitian strategi. Meskipun backtest yang datang dengan platform FMZ sangat berguna, itu tidak cocok untuk strategi dengan volume data yang kompleks dan besar.

Penggunaan Jupyter

Lingkungan penelitian di dalam FMZ dapat digunakan, tetapi jaringan tidak nyaman. Disarankan untuk menginstal pada perangkat Anda sendiri anaconda3, dengan notebook dan perpustakaan terkait yang umum digunakan untuk perhitungan matematika; dapat berbagi lingkungan jaringan lokal, dan memiliki kinerja yang lebih baik.

Tutorial

Ada banyak tutorial online untuk menggunakan keterampilan tertentu dari notebook dan Python. Anda dapat menemukan banyak informasi dengan mencari kata kunci, seperti kuantifikasi Python dan tutorial notebook jupyter. Anda perlu belajar dan menguasai serangkaian dasar seperti crawler, pemrosesan data, backtest, desain strategi, dan plot.

Akuisisi Data

Platform umumnya menyediakan API untuk mendapatkan K-line dengan data sejarah, dan beberapa juga menyediakan data pelaksanaan perdagangan dengan perdagangan.

Selanjutnya, kita akan menunjukkan cara mendapatkan dan menyimpan data K-line dari kontrak abadi di Binance.

Pertama, cari dokumentasi Binance Perpetual Swap:https://binance-docs.github.io/apidocs/futures/cn/#c59e471e81. Anda dapat melihat parameter yang diperlukan dan format data yang dikembalikan. Biasanya, jumlah K-line yang diperoleh oleh API terbatas, dan Binance memiliki maksimum 1000, sehingga perlu diperoleh dengan iterasi loop. Situasi di platform lain mirip dengan Binance. Perhatikan bahwa jaringan perlu terhubung ke jaringan luar negeri (dibandingkan dengan jaringan domestik di Cina) untuk merayapi K-line.

Periode yang didukung Binance:1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M.

Dalam [24]: permintaan impor #permintaan jaringan untuk perpustakaan umum dari tanggal/waktu tanggal impor,tanggal/waktu waktu impor mengimpor panda sebagai pd Di [160]: def GetKlines ((simbol=BTC,start=2020-8-10,end=2021-8-10,period=1h): Klines = [] start_time = int(time.mktime(datetime.strptime(start, %Y-%m-%d).timetuple))) *1000 end_time = int(time.mktime(datetime.strptime(end, %Y-%m-%d).timetuple))) *1000 sementara start_time < end_time: res = request.get ((https://fapi.binance.com/fapi/v1/klines?symbol=%sUSDT&interval=%s&startTime=%s&limit=1000% ((symbol,period,start_time)) res_list = res.json() Klines += res_list #print ((datetime.utcfromtimestamp ((start_time/1000).strftime ((%Y-%m-%d %H:%M:%S),len ((res_list)) start_time = res_list[-1][0] return pd.DataFrame ((Klines,columns=[time,open,high,low,close,amount,end_time,volume,count,buy_amount,buy_volume,null]).astype ((float) Di [85]: df = GetKlines ((simbol=BTC,start=2021-1-1,end=2021-8-10,periode=1h)

Penyimpanan dan pembacaan data dapat menggunakan fungsi di dalam perpustakaan panda. Formatnya adalah csv, yang dapat langsung dibuka dengan perangkat lunak excel.

Selain harga tertinggi, harga terendah, harga terbuka, harga penutupan dan volume yang dilaksanakan, data K-line yang dikembalikan oleh Binance juga mencakup jumlah perdagangan total, jumlah pembelian inisiatif, jumlah eksekusi, dll. Ini adalah informasi berharga yang dapat digunakan untuk membangun strategi.

Di [86]: df.to_csv ((btc_klines.csv) df = pd.read_csv(btc_klines.csv,index_col=0) Di [87]: df Keluar[87]: ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, waktu buka tinggi rendah jumlah tutup akhir_waktu jumlah jumlah beli_jumlah beli_volume nol 0 1596988800000 11575.08 11642.00 11566.07 11591.37 6541.466 1596992399999 7.592336e+07 25724 3127.898 3.630633e+07 0 1 1596992400000 11591.39 11610.23 11526.90 11534.39 6969.252 1596995999999 8.057780e+07 27403 3390.424 3.920162e+07 0 2 1596996000000 11534.39 11656.69 11527.93 11641.07 6439.365 1596999599999 7.469135e+07 25403 3446.186 3.997906e+07 0 3 1596999600000 11641.06 11665.90 11624.20 11635.30 3911.582 1597003199999 4.555459e+07 17820 1842.413 2.145768e+07 0 4 1597003200000 11635.29 11684.00 11635.29 11673.81 3461.004 1597006799999 4.036804e+07 15513 1660.575 1.936981e+07 0 .............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................. 8805 1628658000000 45627.72 45894.53 45540.00 45801.45 10296.202 1628661599999 4.710187e+08 112187 4988.565 2.282399e+08 0 8806 1628661600000 45801.46 46270.00 45800.01 46087.86 26409.962 1628665199999 1.215164e+09 247170 13696.301 6.302708e+08 0 8807 1628665200000 46087.87 46450.00 46087.87 46367.38 23969.309 1628668799999 1.110210e+09 232348 11990.951 5.554267e+08 0 8808 1628668800000 46367.37 46643.13 46002.01 46217.01 23472.769 1628672399999 1.086549e+09 229533 12334.292 5.711837e+08 0 8809 1628672400000 46217.01 46329.69 46046.54 46297.16 6579.477 16286759999 3.039580e+08 78812 3313.055 1.530718e+08 0 ... 8810 baris × 12 kolom

... Di [88]: df.index = pd.to_datetime ((df.time,unit=ms) #mengkonversi indeks menjadi tanggal, yang nyaman untuk plot Di [89]: df.close.plot ((figsize=(15,6), grid = True); #close price Keluar[89]:imgDi [92]: (df.buy_amount.rolling(150).mean()/df.amount.rolling(150.mean)).plot ((figsize=(15,6), grid = True); #setelah rata, proporsi inisiatif membeli jumlah # situasi dimana proporsi jumlah pembelian inisiatif meningkat setelah mencapai titik terendah biasanya merespon situasi kenaikan harga, tetapi rata-rata jangka panjang proporsi jumlah pembelian inisiatif adalah 49% Keluar[92]:imgDi [93]: (df[count].rolling(100).mean (()).plot ((figsize=(15,6),grid = True); #jumlah yang dieksekusi setelah rata,dan kutipan pasar mungkin disiapkan di lokasi yang rendah Keluar[93]:img

Mesin Backtest

Artikel sebelumnya juga memberikan mesin backtest Python, tetapi di sini adalah versi yang dioptimalkan. USDT-margined (atau lain kutipan mata uang-margined) kontrak abadi sangat mirip dengan kontrak spot. Perbedaannya adalah bahwa kontrak abadi dapat dimanfaatkan dan memegang jumlah negatif (setara dengan membuat pendek), dan dapat berbagi mesin backtest. kontrak pengiriman crypto-margined khusus, karena mereka diselesaikan dalam mata uang dan memerlukan backtest khusus.

Di sini diberikan contoh sederhana, yang dapat mengimplementasikan spot multi-simbol atau backtesting perpetual multi-simbol. Banyak detail diabaikan: seperti leverage futures, margin occupation, funding rate, mekanisme likuidasi, market making dan transaksi order taker serta pemeliharaan order, tetapi biasanya tidak mempengaruhi hasil backtest normal. Dan harga dan kuantitas pencocokan, dan pembaruan akun semuanya perlu diimpor dari luar. Pembaca dapat memperbaikinya atas dasar ini.

Pengantar kelas pertukaran:

  • account:USDT menunjukkan mata uang dasar, yang tidak diperlukan; realised_profit: keuntungan dan kerugian yang telah direalisasikan; unrealised_profit: keuntungan dan kerugian yang belum direalisasikan; total: total ekuitas; biaya: biaya penanganan. Untuk pasangan perdagangan lainnya, jumlah (yang merupakan angka negatif ketika melakukan shorting); hold_price: harga kepemilikan; nilai: nilai kepemilikan; harga: harga saat ini.

  • trade_symbols: array dari pasangan perdagangan; Anda juga dapat lulus dalam satu pasangan perdagangan; mata uang penawaran default adalah USDT, tetapi Anda juga dapat menggunakan simbol mata uang penawaran lain untuk backtest.

  • biaya: biaya penyerahan; untuk menjadi sederhana, tidak membedakan pembuat dan penerima.

  • initial_balance: aset awal; jumlah awal dari pasangan perdagangan default adalah 0.

  • Fungsi beli: untuk membeli, yang sesuai dengan membuat panjang dan menutup pendek kontrak abadi, tanpa mekanisme pencocokan.

  • Fungsi jual: untuk menjual.

  • Fungsi pembaruan: untuk memperbarui informasi akun, yang perlu dilewati dalam kamus harga dari semua pasangan perdagangan. Di [98]: kelas Pertukaran:

    def init(self, trade_symbols, fee=0.0004, initial_balance=10000): self.initial_balance = initial_balance #saldo awal biaya sendiri = biaya self.trade_symbols = trade_symbols sendiri.account = {USDT:{realised_profit:0, unrealised_profit:0, total:initial_balance, fee:0}} untuk simbol dalam trade_symbols: self.account[symbol] = {jumlah:0, harga_tahan:0, nilai:0, harga:0, keuntungan_realisasi:0,keuntungan_tidakrealisasi:0,biaya:0}

    def Perdagangan ((sendiri, simbol, arah, harga, jumlah):

      cover_amount = 0 if direction*self.account[symbol]['amount'] >=0 else min(abs(self.account[symbol]['amount']), amount)
      open_amount = amount - cover_amount
      self.account['USDT']['realised_profit'] -= price*amount*self.fee #take out the fee 
      self.account['USDT']['fee'] += price*amount*self.fee
      self.account[symbol]['fee'] += price*amount*self.fee
    
      if cover_amount > 0: #close first 
          self.account['USDT']['realised_profit'] += -direction*(price - self.account[symbol]['hold_price'])*cover_amount  #profit 
          self.account[symbol]['realised_profit'] += -direction*(price - self.account[symbol]['hold_price'])*cover_amount
          
          self.account[symbol]['amount'] -= -direction*cover_amount
          self.account[symbol]['hold_price'] = 0 if self.account[symbol]['amount'] == 0 else self.account[symbol]['hold_price']
          
      if open_amount > 0:
          total_cost = self.account[symbol]['hold_price']*direction*self.account[symbol]['amount'] + price*open_amount
          total_amount = direction*self.account[symbol]['amount']+open_amount
          
          self.account[symbol]['hold_price'] = total_cost/total_amount
          self.account[symbol]['amount'] += direction*open_amount
    

    def Beli ((sendiri, simbol, harga, jumlah):self.Trade(simbol, 1, harga, jumlah)

    def Jual ((sendiri, simbol, harga, jumlah):self.Trade(simbol, -1, harga, jumlah)

    def Update ((self, close_price): #update aset sendiri.account[USDT][unrealised_profit] = 0 untuk simbol dalam self.trade_symbols: self.account[simbol][unrealised_profit] = (close_price[simbol] - self.account[simbol][hold_price]) *self.account[simbol][amount] self.account[simbol][price] = close_price[simbol] self.account[simbol][value] = abs(self.account[simbol][jumlah]) *close_price[simbol] self.account[USDT][unrealised_profit] += self.account[simbol][unrealised_profit] self.account[USDT][total] = bulat(self.account[USDT][realised_profit] + self.initial_balance + self.account[USDT][unrealised_profit],6) Di [117]: #Dalam uji coba, Anda dapat melihat bahwa tidak ada penekanan pada apakah platformnya USDT-margined atau spot. e = Exchange([BTC], biaya=0.0004, initial_balance=10000) #menciptakan objek Exchange, dan hanya satu pasangan perdagangan BTC e.Beli ((BTC,40000, 0.1) #beli 0.1 BTC dengan harga 40.000 e.Menjual ((BTC,41000, 0.1) #menjual 0,1 BTC dengan harga 41.000 e.Update (({BTC:41000}) #updtae informasi akun print ((e.account) #informasi rekening akhir print (("Keuntungan: ',round ((e.account[USDT][total]-e.initial_balance,2)) Out[117]:{USDT: {realised_profit: 96.76, unrealised_profit: 0.0, total: 10096.76, fee: 3.24}, BTC: {amount: 0.0, hold_price: 0, value: 0.0, price: 000, 41 realised_profit: 100.0, unrealised_profit: 0.0, fee: 3.24}} keuntungan: 96,76

Grid Strategi Backtest

Pertama, mari kita backtest strategi grid abadi klasik. Strategi ini sangat populer di platform kami baru-baru ini. Dibandingkan dengan grid spot, tidak perlu memegang mata uang dan dapat menambahkan leverage, yang jauh lebih nyaman daripada grid spot. Namun, karena tidak dapat langsung backtest, tidak kondusif untuk memilih simbol mata uang. Di sini kita menggunakan mesin backtest sekarang untuk mengujinya.

Di bagian atas Live, ada bot resmi, dimulai dari tanggal 4 April 2021; nilai posisi adalah 150, jarak kisi adalah 0.01, dan keuntungan saat ini adalah 3600USDT. Menggunakan parameter yang sama dan 5min K-line untuk backtest, keuntungan adalah 3937USDT. Karena nilai posisi di awal bot kurang dari 150 USDT, hasilnya cukup akurat. Jika Anda mengubah jarak kisi menjadi 0.005, keuntungan akan menjadi 5226U. Jarak kisi 0,005 jelas merupakan parameter yang lebih baik daripada 0,01, yang perlu backtest untuk mengetahui.

Semakin pendek periode K-line, semakin akurat hasil backtest yang sesuai, dan semakin besar jumlah data yang diperlukan.

Di [241]: simbol = TRX df = GetKlines ((simbol=simbol,start=2021-4-4,end=2021-8-11,period=5m) Di [286]: nilai = 150 pct = 0,01

e = Exchange (([simbol], biaya=0.0002, initial_balance=10000) init_price = df.loc[0,close] res_list = [] # Digunakan untuk menyimpan hasil tengah untuk baris di df.iterrows(): kline = baris[1] #yang hanya akan menguji satu K-line dan hanya mendapatkan satu buy order atau satu sell order, yang tidak terlalu akurat buy_price = (value / pct - value) / ((value / pct) / init_price + e.account[simbol][amount]) #sell order price, karena ini adalah eksekusi pembuat, juga merupakan harga pencocokan akhir sell_price = (value / pct + value) / ((value / pct) / init_price + e.account[simbol][amount])

if kline.low < buy_price: #the lowest price of K-line is less than the current maker price; the buy order is executed 
    e.Buy(symbol,buy_price,value/buy_price)
if kline.high > sell_price:
    e.Sell(symbol,sell_price,value/sell_price)
e.Update({symbol:kline.close})
res_list.append([kline.time, kline.close, e.account[symbol]['amount'], e.account['USDT']['total']-e.initial_balance])

res = pd.DataFrame ((data=res_list, kolom=[time,price,amount,profit]) res.index = pd.to_datetime ((res.time,unit=ms) Di [287]: e.rekening Out[287]:{USDT: {realised_profit: 3866.633149565143, keuntungan tidak direalisasikan: 70.54622281993666, total: 13937.179372, biaya: 177,51000000000596}, TRX: {jumlah: 36497.43208747655, hold_price: 0,08203709078461048, nilai: 3064.689372385406, harga: 0,08397, realised_profit: 4044.143149565462, keuntungan tidak direalisasikan: 70.54622281993666, biaya: 177.51000000000596}} Di [288]: res.profit.plot ((figsize=(15,6), grid = True); Keluar[288]:imgDalam [170]: res.price.plot ((figsize=(15,6), grid = True); #close price Keluar[170]:img

Spot Equilibrium Strategy Backtest

Jenis strategi ini juga relatif populer, tetapi platform FMZ tidak sangat baik dalam backtesting strategi multi-simbol, cukup gunakan mesin backtest ini untuk mencobanya. Kami memilih empat simbol mata uang utama, BTC, ETH, LTC, dan XRP, dan mengkonfigurasi 25% dari nilai pasar masing-masing, dan menyeimbangkan setiap penyimpangan 1%.

Pertama, dapatkan harga penutupan dari empat simbol dalam setahun terakhir. Hal ini dapat dilihat bahwa ETH memiliki kenaikan terbesar, dan tiga lainnya memiliki kenaikan yang sama. Jika Anda memegang empat simbol ini rata-rata, nilai bersih akhir adalah 4.5. Setelah backtest, strategi keseimbangan memiliki nilai bersih akhir 5.3, yang sedikit ditingkatkan.

Di [290]: simbol = [BTC,ETH,LTC,XRP] data = {} untuk simbol dalam simbol: df = GetKlines ((simbol=simbol,start=2020-8-11,end=2021-8-11,period=1h) data[simbol] = df.close Di [291]: df = pd.DataFrame (([data[simbol].nilai untuk simbol dalam simbol],index=simbol).T Dalam [302]: e = Pertukaran ((simbol, biaya=0.0004, initial_balance=10000) res_list = [] untuk baris di df.iterrows(): harga = baris[1] total = e.account[USDT][total] e.Meningkatkan harga) untuk simbol dalam simbol: pct = e.account[simbol][value]/total jika pct > 0,26: e.Menjual ((simbol,harga[simbol],(posisi-0.25)*total/harga[simbol]) jika pct < 0,24: e.Beli (simbol,harga (simbol), (simbol), (simbol) res_list.append (([e.account[simbol][value] untuk simbol dalam simbol] + [e.account[USDT][total]]) res = pd.DataFrame ((data=res_list, kolom=simbol+[total]) Di [303]: (df/df.iloc[0,:]).plot(figsize=(15,6),grid = True); #plot trand dengan normalisasi Keluar[303]:imgDi [304]: (res.total/10000-(df/df.iloc[0,:]).mean(axis=1)).plot(figsize=(15,6),grid = True); #enheance efek Keluar [1]:img

Strategi Penyu

Strategi penyu adalah strategi tren klasik yang mencakup logika stop-loss lengkap untuk menambahkan posisi.https://zhuanlan.zhihu.com/p/27987938Kami akan menerapkan versi sederhana di sini untuk backtest.

Periode strategi penyu memiliki pengaruh besar pada strategi, dan tidak disarankan untuk memilih periode yang terlalu pendek. Di sini, kita memilih 6h. Periode saluran Donchian dipilih sebagai 5, dan rasio posisi dipilih sebagai 0,003 sesuai dengan backtest. Ketika harga menembus upBand saluran untuk membuka 1 unit posisi panjang, dan harga terus naik dengan volatilitas 0,3 setelah membuka posisi, terus tambahkan 1 unit, dan harga turun di bawah 2,5 Volatilitas harga terbuka terbaru untuk menghentikan kerugian. Prinsip pesanan pendek sama. Karena pasar bull besar ETH, strategi penyu telah menangkap tren utama dan akhirnya mencapai 27 kali keuntungan, dengan leverage maksimum 4 kali selama periode tersebut.

Parameter strategi kura-kura terkait erat dengan periode, dan mereka perlu dipilih melalui backtest.

Dari grafik nilai bersih akhir dapat dilihat bahwa strategi penyu adalah strategi jangka panjang, di mana mungkin tidak ada keuntungan selama 3 sampai 4 bulan, dan kehilangan berhenti berulang, tetapi setelah ada penawaran pasar yang besar di satu sisi, strategi penyu dapat memanfaatkan tren untuk mengumpulkan posisi besar, menahannya hingga akhir tren, menghasilkan banyak keuntungan. Pada akhir kenaikan, strategi akan mengumpulkan banyak posisi. Pada saat ini, volatilitas akan relatif besar, dan seringkali keuntungan besar akan ditarik kembali. Menggunakan strategi penyu mengharuskan Anda menerima kekurangannya dan kesabaran Anda.

Di [424]: simbol = ETH df = GetKlines ((simbol=simbol,start=2019-8-11,end=2021-8-11,period=6h) Dalam [425]: df.index = pd.to_datetime ((df.time,unit=ms) Di [568]: M = 5 # volume periode saluran Donchian pct = 0,003 #proporsi posisi yang ditambahkan dalam total posisi df[up] = df[high].rolling ((M).max().shift(1) #upBand dari saluran Donchian, digunakan untuk membuat panjang dan menilai untuk menerobos t df[down] = df[low].rolling(M).max().shift(1) df[middle] = (df[up]+df[down])/2 df[true_range] = pd.concat([df[high]-df[low],df[high]-df[close].shift(1),df[close].shift(1)-df[low],axis=1).max(axis=1) df[N] = df[true_range].rolling(50).mean() #N sama dengan volatilitas baru-baru ini, digunakan untuk menilai untuk membeli dan menghentikan kerugian Di [572]: open_times = 0.3 #penghakiman pembukaan posisi stop_times = 2,5 #stop loss e = Exchange([simbol], fee=0.0004, initial_balance=10000) #set the taker to 0.0004 res_list = [] last_price = 0 #harga posisi terbuka terakhir untuk baris di df.iterrows(): Kline = baris[1] jika kline.isnull (().sum (()) > 0: #lewati bagian tanpa data Lanjutkan Unit = e.account[USDT][total]*pct/kline.N #jumlah unit posisi terbuka

if kline.high >  kline.up and e.account[symbol]['amount'] == 0: #first time to open long position 
    e.Buy(symbol,kline.up,unit) #notice the trading price here
    last_price = kline.up
if e.account[symbol]['amount'] > 0 and kline.high > last_price + open_times*kline.N: #long position, buy in 
    e.Buy(symbol,last_price + open_times*kline.N,unit)
    last_price = last_price + open_times*kline.N
if e.account[symbol]['amount'] > 0 and kline.low < last_price - stop_times*kline.N: #long position, stop loss
    e.Sell(symbol,last_price - stop_times*kline.N,e.account[symbol]['amount'])
    
if kline.low <  kline.down and e.account[symbol]['amount'] == 0: #open short
    e.Sell(symbol,kline.down,unit)
    last_price = kline.down
if e.account[symbol]['amount'] < 0 and kline.low < last_price - open_times*kline.N: #short position, buy in 
    e.Sell(symbol,last_price - open_times*kline.N,unit)
    last_price = last_price - open_times*kline.N
if e.account[symbol]['amount'] < 0 and kline.high > last_price + stop_times*kline.N: #short position, stop loss
    e.Buy(symbol,last_price + stop_times*kline.N,-e.account[symbol]['amount'])
    
e.Update({symbol:kline.close})
res_list.append([kline.time, kline.close, e.account[symbol]['amount']*kline.close, e.account['USDT']['total']])

res = pd.DataFrame ((data=res_list, kolom=[time,price,value,total]) res.index = pd.to_datetime ((res.time,unit=ms) print (( Nilai pasar akhir:,res[total][-1]) Out[572]: Nilai pasar akhir: 280760.566996 Di [573]: res.total.plot ((figsize=(15,6), grid = True); Keluar[573]:imgDi [571]: (res.value/res.total).plot ((figsize=(15,6), grid = True); Keluar [1]:img

Kesimpulan

Jika Anda mahir menggunakan platform penelitian notebook jupyter, Anda dapat dengan mudah melakukan operasi, seperti akuisisi data, analisis data, backtest strategi, tampilan grafik, dll, yang merupakan cara yang tak terelakkan untuk perdagangan kuantitatif.

Gunakan Python untuk melakukan analisis data:https://wizardforcel.gitbooks.io/pyda-2e/content/

Tutorial kuantitatif Python:https://wizardforcel.gitbooks.io/python-quant-uqer/content/

Dalam [ ]:


Lebih banyak