পাইথনের সাথে ইভেন্ট-চালিত ব্যাকটেস্টিং - পার্ট V

লেখক:ভাল, তৈরিঃ 2019-03-25 15:54:16, আপডেটঃ

ইভেন্ট-চালিত ব্যাকটেস্টিং সম্পর্কিত পূর্ববর্তী নিবন্ধে আমরা কীভাবে একটি কৌশল শ্রেণীর শ্রেণিবিন্যাস তৈরি করব তা বিবেচনা করেছি। এখানে সংজ্ঞায়িত কৌশলগুলি সংকেত উত্পন্ন করতে ব্যবহৃত হয়, যা একটি পোর্টফোলিও অবজেক্ট দ্বারা অর্ডার প্রেরণের বিষয়ে সিদ্ধান্ত নেওয়ার জন্য ব্যবহৃত হয়। আগের মতোই এটি একটি পোর্টফোলিও বিমূর্ত বেস ক্লাস (এবিসি) তৈরি করা স্বাভাবিক যা পরবর্তী সমস্ত উপ-শ্রেণীর উত্তরাধিকার হয়।

এই নিবন্ধটি একটি NaivePortfolio অবজেক্ট বর্ণনা করে যা একটি পোর্টফোলিওর মধ্যে অবস্থানের ট্র্যাক রাখে এবং সংকেতগুলির উপর ভিত্তি করে একটি নির্দিষ্ট পরিমাণ স্টক অর্ডার তৈরি করে। পরবর্তী পোর্টফোলিও অবজেক্টগুলিতে আরও পরিশীলিত ঝুঁকি ব্যবস্থাপনা সরঞ্জাম অন্তর্ভুক্ত থাকবে এবং পরবর্তী নিবন্ধগুলির বিষয় হবে।

অবস্থান ট্র্যাকিং এবং অর্ডার ম্যানেজমেন্ট

পোর্টফোলিও অর্ডার ম্যানেজমেন্ট সিস্টেম সম্ভবত ইভেন্ট-চালিত ব্যাকটেস্টারের সবচেয়ে জটিল উপাদান। এর ভূমিকা হ'ল সমস্ত বর্তমান বাজার অবস্থানগুলি পাশাপাশি অবস্থানগুলির বাজার মূল্য (হোল্ডিং হিসাবে পরিচিত) ট্র্যাক করা। এটি কেবল অবস্থানের তরলীকরণের মূল্যের একটি অনুমান এবং অংশে ব্যাকটেস্টারের ডেটা হ্যান্ডলিং সুবিধা থেকে প্রাপ্ত।

পজিশন এবং হোল্ডিং ম্যানেজমেন্টের পাশাপাশি, ব্রোকারেজ বা বাজারে প্রবেশাধিকারের অন্য কোনও ফর্মের কাছে প্রেরিত অর্ডারগুলিকে অনুকূল করার জন্য পোর্টফোলিওতে ঝুঁকি কারণ এবং পজিশন সাইজিং কৌশলগুলি সম্পর্কেও সচেতন হতে হবে।

ইভেন্ট ক্লাসের শ্রেণিবিন্যাসের ধারা অব্যাহত রেখে একটি পোর্টফোলিও অবজেক্টকে সিগন্যাল ইভেন্ট অবজেক্টগুলি পরিচালনা করতে, অর্ডার ইভেন্ট অবজেক্টগুলি তৈরি করতে এবং অবস্থানগুলি আপডেট করতে ফিল ইভেন্ট অবজেক্টগুলি ব্যাখ্যা করতে সক্ষম হতে হবে। সুতরাং পোর্টফোলিও অবজেক্টগুলি প্রায়শই ইভেন্ট-চালিত সিস্টেমের বৃহত্তম উপাদান, কোডের লাইনের (এলওসি) দিক থেকে।

বাস্তবায়ন

আমরা একটি নতুন ফাইল তৈরি করিportfolio.pyএবং প্রয়োজনীয় লাইব্রেরী আমদানি করুন। এগুলি অন্যান্য বিমূর্ত বেস ক্লাস বাস্তবায়নগুলির মতোই। পূর্ণসংখ্যার মানের অর্ডার আকার তৈরি করতে আমাদের গণিত লাইব্রেরি থেকে ফ্লোর ফাংশন আমদানি করতে হবে। পোর্টফোলিও উভয়ই পরিচালনা করে তাই আমাদের ফিলইভেন্ট এবং অর্ডারইভেন্ট অবজেক্টগুলিও দরকার।

# portfolio.py

import datetime
import numpy as np
import pandas as pd
import Queue

abc আমদানি থেকে ABCMeta, বিমূর্ত পদ্ধতি গণিত আমদানি তল থেকে

ঘটনা আমদানি থেকে FillEvent, OrderEvent আগে যেমন আমরা পোর্টফোলিও জন্য একটি ABC তৈরি এবং দুটি বিশুদ্ধ ভার্চুয়াল পদ্ধতি update_signal এবং update_fill আছে। প্রথমটি ইভেন্ট সারি থেকে ধরা হচ্ছে নতুন ট্রেডিং সংকেত হ্যান্ডেল এবং দ্বিতীয়টি একটি এক্সিকিউশন হ্যান্ডলার বস্তু থেকে প্রাপ্ত পূরণ হ্যান্ডেল।

# portfolio.py

class Portfolio(object):
    """
    The Portfolio class handles the positions and market
    value of all instruments at a resolution of a "bar",
    i.e. secondly, minutely, 5-min, 30-min, 60 min or EOD.
    """

    __metaclass__ = ABCMeta

    @abstractmethod
    def update_signal(self, event):
        """
        Acts on a SignalEvent to generate new orders 
        based on the portfolio logic.
        """
        raise NotImplementedError("Should implement update_signal()")

    @abstractmethod
    def update_fill(self, event):
        """
        Updates the portfolio current positions and holdings 
        from a FillEvent.
        """
        raise NotImplementedError("Should implement update_fill()")

এই নিবন্ধের প্রধান বিষয় হল NaivePortfolio ক্লাস। এটি পজিশন সাইজিং এবং বর্তমান হোল্ডিং পরিচালনা করার জন্য ডিজাইন করা হয়েছে, তবে কেবলমাত্র একটি পূর্ব নির্ধারিত স্থির পরিমাণের আকারের সাথে ব্রোকারেজে সরাসরি পাঠিয়ে একটি dumb পদ্ধতিতে ট্রেডিং অর্ডার সম্পাদন করবে। এই সমস্ত নগদ ধারণ করা হয়। এগুলি সমস্ত অবাস্তব অনুমান, তবে তারা কীভাবে একটি পোর্টফোলিও অর্ডার ম্যানেজমেন্ট সিস্টেম (ওএমএস) ইভেন্ট-চালিত ফ্যাশনে কাজ করে তা রূপরেখা করতে সহায়তা করে।

NaivePortfolio এর জন্য একটি প্রাথমিক মূলধন মূল্য প্রয়োজন, যা আমি ডিফল্টরূপে 100,000 USD এ সেট করেছি। এর জন্য একটি শুরু তারিখ-সময়ও প্রয়োজন।

পোর্টফোলিওতে সমস্ত_পজিশন এবং বর্তমান_পজিশন সদস্য রয়েছে। প্রথমটি বাজারের ডেটা ইভেন্টের টাইমস্ট্যাম্পে রেকর্ড করা সমস্ত পূর্ববর্তী অবস্থানগুলির একটি তালিকা সঞ্চয় করে। একটি অবস্থান কেবল সম্পদের পরিমাণ। নেতিবাচক অবস্থানগুলির অর্থ সম্পত্তিটি শর্ট হয়েছে। শেষ সদস্যটি সর্বশেষ বাজার বার আপডেটের জন্য বর্তমান অবস্থানগুলি ধারণকারী একটি অভিধান সঞ্চয় করে।

পোর্টফোলিওতে পজিশন সদস্যদের পাশাপাশি হোল্ডিংগুলিও সংরক্ষণ করা হয়, যা হোল্ডিংগুলির বর্তমান বাজার মূল্যকে বর্ণনা করে। বর্তমান বাজার মূল্য এই ক্ষেত্রে বর্তমান বাজার বার থেকে প্রাপ্ত বন্ধের মূল্য বোঝায়, যা স্পষ্টতই একটি আনুমানিক, তবে এই মুহুর্তে যথেষ্ট যুক্তিসঙ্গত। all_holdings সমস্ত প্রতীক হোল্ডিংয়ের ঐতিহাসিক তালিকা সংরক্ষণ করে, যখন current_holdings সমস্ত প্রতীক হোল্ডিংয়ের মানগুলির সর্বাধিক আপ টু ডেট অভিধান সংরক্ষণ করে।

# portfolio.py

class NaivePortfolio(Portfolio):
    """
    The NaivePortfolio object is designed to send orders to
    a brokerage object with a constant quantity size blindly,
    i.e. without any risk management or position sizing. It is
    used to test simpler strategies such as BuyAndHoldStrategy.
    """
    
    def __init__(self, bars, events, start_date, initial_capital=100000.0):
        """
        Initialises the portfolio with bars and an event queue. 
        Also includes a starting datetime index and initial capital 
        (USD unless otherwise stated).

        Parameters:
        bars - The DataHandler object with current market data.
        events - The Event Queue object.
        start_date - The start date (bar) of the portfolio.
        initial_capital - The starting capital in USD.
        """
        self.bars = bars
        self.events = events
        self.symbol_list = self.bars.symbol_list
        self.start_date = start_date
        self.initial_capital = initial_capital
        
        self.all_positions = self.construct_all_positions()
        self.current_positions = dict( (k,v) for k, v in [(s, 0) for s in self.symbol_list] )

        self.all_holdings = self.construct_all_holdings()
        self.current_holdings = self.construct_current_holdings()

নিম্নলিখিত পদ্ধতি, construct_all_positions, কেবল প্রতিটি চিহ্নের জন্য একটি অভিধান তৈরি করে, প্রতিটি জন্য মানটি শূন্যে সেট করে এবং তারপরে একটি তারিখ-সময় কী যুক্ত করে, অবশেষে এটি একটি তালিকায় যুক্ত করে। এটি একটি অভিধান বোঝার ব্যবহার করে, যা একটি তালিকার বোঝার মতোঃ

# portfolio.py

    def construct_all_positions(self):
        """
        Constructs the positions list using the start_date
        to determine when the time index will begin.
        """
        d = dict( (k,v) for k, v in [(s, 0) for s in self.symbol_list] )
        d['datetime'] = self.start_date
        return [d]

construct_all_holdings পদ্ধতিটি উপরের অনুরূপ তবে নগদ, কমিশন এবং মোটের জন্য অতিরিক্ত কী যুক্ত করে যা যথাক্রমে যে কোনও ক্রয়ের পরে অ্যাকাউন্টে অতিরিক্ত নগদ, জমে থাকা সমষ্টিগত কমিশন এবং নগদ এবং কোনও খোলা অবস্থান সহ মোট অ্যাকাউন্টের মূলধনকে উপস্থাপন করে। শর্ট পজিশনগুলি নেতিবাচক হিসাবে চিকিত্সা করা হয়। প্রারম্ভিক নগদ এবং মোট অ্যাকাউন্টের মূলধন উভয়ই প্রাথমিক মূলধনের জন্য সেট করা হয়ঃ

# portfolio.py

    def construct_all_holdings(self):
        """
        Constructs the holdings list using the start_date
        to determine when the time index will begin.
        """
        d = dict( (k,v) for k, v in [(s, 0.0) for s in self.symbol_list] )
        d['datetime'] = self.start_date
        d['cash'] = self.initial_capital
        d['commission'] = 0.0
        d['total'] = self.initial_capital
        return [d]

নিম্নলিখিত পদ্ধতি, construct_current_holdings উপরের পদ্ধতির সাথে প্রায় একই, তবে এটি একটি তালিকায় অভিধানটি আবৃত করে নাঃ

# portfolio.py

    def construct_current_holdings(self):
        """
        This constructs the dictionary which will hold the instantaneous
        value of the portfolio across all symbols.
        """
        d = dict( (k,v) for k, v in [(s, 0.0) for s in self.symbol_list] )
        d['cash'] = self.initial_capital
        d['commission'] = 0.0
        d['total'] = self.initial_capital
        return d

প্রতিটি হার্টবিট এ, অর্থাৎ প্রতিবার ডেটাহ্যান্ডলার অবজেক্ট থেকে নতুন বাজার ডেটা অনুরোধ করা হলে, পোর্টফোলিওকে সমস্ত অবস্থানগুলির বর্তমান বাজার মূল্য আপডেট করতে হবে। লাইভ ট্রেডিং দৃশ্যকল্পে এই তথ্য সরাসরি ব্রোকারেজ থেকে ডাউনলোড এবং বিশ্লেষণ করা যেতে পারে, তবে ব্যাকটেস্টিং বাস্তবায়নের জন্য এই মানগুলি ম্যানুয়ালি গণনা করা প্রয়োজন।

দুর্ভাগ্যবশত, বিড/এন্ড স্প্রেড এবং তরলতা সংক্রান্ত ইস্যুগুলির কারণে বর্তমান বাজার মূল্য এর মতো কিছু নেই। সুতরাং এটি একটি মূল্য দ্বারা রাখা সম্পদের পরিমাণকে গুণ করে এটি অনুমান করা প্রয়োজন। আমি এখানে যে পদ্ধতি গ্রহণ করেছি তা হ'ল সর্বশেষ প্রাপ্ত বারটির বন্ধের মূল্য ব্যবহার করা। একটি ইনট্রা-ডে কৌশলটির জন্য এটি তুলনামূলকভাবে বাস্তবসম্মত। একটি দৈনিক কৌশলটির জন্য এটি কম বাস্তবসম্মত কারণ উদ্বোধনী মূল্য বন্ধের মূল্য থেকে উল্লেখযোগ্যভাবে পৃথক হতে পারে।

পদ্ধতি update_timeindex নতুন হোল্ডিং ট্র্যাকিং পরিচালনা করে। এটি প্রথমে বাজার ডেটা হ্যান্ডলার থেকে সর্বশেষ মূল্যগুলি অর্জন করে এবং current_positions[s] * bars[s][0][5]) এর সমান current_positions সেট করে বর্তমান অবস্থানের প্রতিনিধিত্ব করার জন্য একটি নতুন প্রতীক অভিধান তৈরি করে। এগুলি কেবলমাত্র যখন একটি FillEvent পাওয়া যায় তখনই পরিবর্তিত হয়, যা পরে পোর্টফোলিওতে পরিচালিত হয়। পদ্ধতিটি তারপরে বর্তমান অবস্থানের এই সেটটি all_positions তালিকায় সংযুক্ত করে। এরপরে হোল্ডিংগুলি অনুরূপ উপায়ে আপডেট করা হয়, তবে বাজার মূল্যটি সর্বশেষ বারটির বন্ধ মূল্যের সাথে বর্তমান অবস্থানগুলিকে গুণ করে পুনরায় গণনা করা হয় (self.current_positions[s] * bars[s][0][5]) । অবশেষে নতুন হোল্ডিংগুলি সমস্ত_হোল্ডিংগুলিতে সংযুক্ত করা হয়ঃ

# portfolio.py

    def update_timeindex(self, event):
        """
        Adds a new record to the positions matrix for the current 
        market data bar. This reflects the PREVIOUS bar, i.e. all
        current market data at this stage is known (OLHCVI).

        Makes use of a MarketEvent from the events queue.
        """
        bars = {}
        for sym in self.symbol_list:
            bars[sym] = self.bars.get_latest_bars(sym, N=1)

        # Update positions
        dp = dict( (k,v) for k, v in [(s, 0) for s in self.symbol_list] )
        dp['datetime'] = bars[self.symbol_list[0]][0][1]

        for s in self.symbol_list:
            dp[s] = self.current_positions[s]

        # Append the current positions
        self.all_positions.append(dp)

        # Update holdings
        dh = dict( (k,v) for k, v in [(s, 0) for s in self.symbol_list] )
        dh['datetime'] = bars[self.symbol_list[0]][0][1]
        dh['cash'] = self.current_holdings['cash']
        dh['commission'] = self.current_holdings['commission']
        dh['total'] = self.current_holdings['cash']

        for s in self.symbol_list:
            # Approximation to the real value
            market_value = self.current_positions[s] * bars[s][0][5]
            dh[s] = market_value
            dh['total'] += market_value

        # Append the current holdings
        self.all_holdings.append(dh)

পদ্ধতি update_positions_from_fill একটি FillEvent কেনা বা বিক্রি হয় কিনা তা নির্ধারণ করে এবং তারপর শেয়ারের সঠিক পরিমাণ যোগ / বিয়োগ করে সেই অনুযায়ী current_positions অভিধান আপডেট করেঃ

# portfolio.py

    def update_positions_from_fill(self, fill):
        """
        Takes a FilltEvent object and updates the position matrix
        to reflect the new position.

        Parameters:
        fill - The FillEvent object to update the positions with.
        """
        # Check whether the fill is a buy or sell
        fill_dir = 0
        if fill.direction == 'BUY':
            fill_dir = 1
        if fill.direction == 'SELL':
            fill_dir = -1

        # Update positions list with new quantities
        self.current_positions[fill.symbol] += fill_dir*fill.quantity

সংশ্লিষ্ট update_holdings_from_fill উপরের পদ্ধতির অনুরূপ কিন্তু পরিবর্তে হোল্ডিং মানগুলি আপডেট করে। একটি ফিলিংয়ের খরচ সিমুলেট করার জন্য, নিম্নলিখিত পদ্ধতিটি FillEvent থেকে যুক্ত খরচ ব্যবহার করে না। কেন এটি? সহজভাবে বলতে গেলে, ব্যাকটেস্টিং পরিবেশে ফিলিংয়ের খরচ আসলে অজানা এবং তাই এটি অনুমান করা আবশ্যক। সুতরাং ফিলিংয়ের খরচ বর্তমান বাজার মূল্য (শেষ বারের বন্ধ মূল্য) এ সেট করা হয়। একটি নির্দিষ্ট প্রতীকের জন্য হোল্ডিংগুলি তারপরে বর্তমান বাজার মূল্য (শেষ বারের বন্ধ মূল্য) এ সেট করা হয়।

একবার ভরাট খরচ জানা গেলে বর্তমান হোল্ডিং, নগদ এবং মোট মানগুলি আপডেট করা যেতে পারে। সমষ্টিগত কমিশনও আপডেট করা হয়ঃ

# portfolio.py

    def update_holdings_from_fill(self, fill):
        """
        Takes a FillEvent object and updates the holdings matrix
        to reflect the holdings value.

        Parameters:
        fill - The FillEvent object to update the holdings with.
        """
        # Check whether the fill is a buy or sell
        fill_dir = 0
        if fill.direction == 'BUY':
            fill_dir = 1
        if fill.direction == 'SELL':
            fill_dir = -1

        # Update holdings list with new quantities
        fill_cost = self.bars.get_latest_bars(fill.symbol)[0][5]  # Close price
        cost = fill_dir * fill_cost * fill.quantity
        self.current_holdings[fill.symbol] += cost
        self.current_holdings['commission'] += fill.commission
        self.current_holdings['cash'] -= (cost + fill.commission)
        self.current_holdings['total'] -= (cost + fill.commission)

পোর্টফোলিও এবিসি থেকে খাঁটি ভার্চুয়াল আপডেট_ফিল পদ্ধতিটি এখানে বাস্তবায়িত হয়। এটি কেবলমাত্র পূর্ববর্তী দুটি পদ্ধতি, আপডেট_পজিশন_ফ্রম_ফিল এবং আপডেট_হোল্ডিংস_ফ্রম_ফিল, যা ইতিমধ্যে উপরে আলোচনা করা হয়েছে তা কার্যকর করেঃ

# portfolio.py

    def update_fill(self, event):
        """
        Updates the portfolio current positions and holdings 
        from a FillEvent.
        """
        if event.type == 'FILL':
            self.update_positions_from_fill(event)
            self.update_holdings_from_fill(event)

যদিও পোর্টফোলিও অবজেক্টকে ফিল ইভেন্টগুলি পরিচালনা করতে হবে, তবে এটিকে এক বা একাধিক সিগন্যাল ইভেন্ট প্রাপ্তির পরে অর্ডার ইভেন্টগুলি তৈরির যত্ন নিতে হবে। জেনারেট_নাইভ_অর্ডার পদ্ধতিটি কেবল একটি সম্পদকে দীর্ঘ বা সংক্ষিপ্ত করার জন্য একটি সংকেত নেয় এবং তারপরে এই জাতীয় সম্পদের 100 শেয়ারের জন্য এটি করার জন্য একটি অর্ডার প্রেরণ করে। স্পষ্টতই 100 একটি স্বতঃস্ফূর্ত মান। একটি বাস্তবসম্মত বাস্তবায়নে এই মানটি ঝুঁকি ব্যবস্থাপনা বা অবস্থান আকারের ওভারলে দ্বারা নির্ধারিত হবে। তবে এটি একটি নাইভপোর্টফোলিও এবং তাই এটি ঝুঁকি সিস্টেম ছাড়াই সরাসরি সংকেতগুলি থেকে সমস্ত অর্ডার প্রেরণ করে।

পদ্ধতিটি বর্তমান পরিমাণ এবং নির্দিষ্ট প্রতীকের উপর ভিত্তি করে একটি অবস্থানের আকাঙ্ক্ষা, শর্ট এবং প্রস্থান পরিচালনা করে। সংশ্লিষ্ট অর্ডার ইভেন্ট অবজেক্টগুলি তখন তৈরি করা হয়ঃ

# portfolio.py

    def generate_naive_order(self, signal):
        """
        Simply transacts an OrderEvent object as a constant quantity
        sizing of the signal object, without risk management or
        position sizing considerations.

        Parameters:
        signal - The SignalEvent signal information.
        """
        order = None

        symbol = signal.symbol
        direction = signal.signal_type
        strength = signal.strength

        mkt_quantity = floor(100 * strength)
        cur_quantity = self.current_positions[symbol]
        order_type = 'MKT'

        if direction == 'LONG' and cur_quantity == 0:
            order = OrderEvent(symbol, order_type, mkt_quantity, 'BUY')
        if direction == 'SHORT' and cur_quantity == 0:
            order = OrderEvent(symbol, order_type, mkt_quantity, 'SELL')   
    
        if direction == 'EXIT' and cur_quantity > 0:
            order = OrderEvent(symbol, order_type, abs(cur_quantity), 'SELL')
        if direction == 'EXIT' and cur_quantity < 0:
            order = OrderEvent(symbol, order_type, abs(cur_quantity), 'BUY')
        return order

আপডেট_সিগন্যাল পদ্ধতিটি কেবল উপরের পদ্ধতিটি কল করে এবং ইভেন্টের সারিতে উত্পন্ন ক্রম যোগ করেঃ

# portfolio.py

    def update_signal(self, event):
        """
        Acts on a SignalEvent to generate new orders 
        based on the portfolio logic.
        """
        if event.type == 'SIGNAL':
            order_event = self.generate_naive_order(event)
            self.events.put(order_event)

নিভ পোর্টফোলিওতে চূড়ান্ত পদ্ধতিটি হ'ল একটি ইক্যুইটি বক্ররেখা তৈরি করা। এটি কেবলমাত্র একটি রিটার্ন স্ট্রিম তৈরি করে, যা পারফরম্যান্স গণনার জন্য দরকারী এবং তারপরে ইক্যুইটি বক্ররেখাকে শতাংশ ভিত্তিক হিসাবে স্বাভাবিক করে তোলে। সুতরাং অ্যাকাউন্টের প্রাথমিক আকার 1.0 এর সমানঃ

# portfolio.py

    def create_equity_curve_dataframe(self):
        """
        Creates a pandas DataFrame from the all_holdings
        list of dictionaries.
        """
        curve = pd.DataFrame(self.all_holdings)
        curve.set_index('datetime', inplace=True)
        curve['returns'] = curve['total'].pct_change()
        curve['equity_curve'] = (1.0+curve['returns']).cumprod()
        self.equity_curve = curve

পোর্টফোলিও অবজেক্টটি পুরো ইভেন্ট-চালিত ব্যাকটেস্ট সিস্টেমের সবচেয়ে জটিল দিক। এখানে বাস্তবায়ন, যদিও জটিল, এটি অবস্থানের হ্যান্ডলিংয়ে তুলনামূলকভাবে মৌলিক। পরবর্তী সংস্করণগুলি ঝুঁকি ব্যবস্থাপনা এবং অবস্থানের আকারকে বিবেচনা করবে, যা কৌশল কর্মক্ষমতা সম্পর্কে আরও বাস্তবসম্মত ধারণা নিয়ে আসবে।

পরবর্তী নিবন্ধে আমরা ইভেন্ট-চালিত ব্যাকটেস্টারের চূড়ান্ত অংশটি বিবেচনা করব, যথা একটি এক্সিকিউশনহ্যান্ডলার অবজেক্ট, যা অর্ডারইভেন্ট অবজেক্টগুলি নিতে এবং সেগুলি থেকে ফিলইভেন্ট অবজেক্টগুলি তৈরি করতে ব্যবহৃত হয়।


আরো