投资对象是公开交易的流动性良好的足球俱乐部股票。投资者在比赛前一天收盘时,对参与欧洲冠军联赛或其他重要比赛的俱乐部股票进行空头操作。股票持有时间为一天,若当天有多家俱乐部比赛,则这些股票将被等权重分配。

策略概述

投资对象是公开交易的流动性良好的足球俱乐部股票。投资者在比赛前一天收盘时,对参与欧洲冠军联赛或其他重要比赛的俱乐部股票进行空头操作。股票持有时间为一天,若当天有多家俱乐部比赛,则这些股票将被等权重分配。

策略合理性

学术论文提出,股票市场对足球比赛结果反应不灵敏的一个重要原因,是投资者对比赛结果的概率分布存在系统性偏差。博彩公司赔率由一小部分专家制定,未能反映投资者的主观信念,而股票价格则能够反映这些信念。

论文来源

Understanding Investor Sentiment: The Case of Soccer [点击浏览原文]

<摘要>

我们研究了投资者对未来事件结果的偏见性预期是否能部分解释股票市场对不确定性消除的无效反应。在实验中,我们分析了公开交易的欧洲足球俱乐部股票在重要比赛前后的回报率。通过使用一种基于博彩市场(预测市场)的新型投资者预期代理变量,我们发现投资者情绪部分源自其预期的系统性偏差。投资者在赛前过于乐观,而在赛后大多感到失望,导致赛后出现负的异常回报。我们的证据可能对公司的投资决策和企业控制交易产生重要影响。

回测表现

年化收益率42%
波动率50%
Beta-0.019
夏普比率0.506
索提诺比率N/A
最大回撤47.8%
胜率48%

完整python代码

from AlgoLib import *
#endregion

class SoccerClubsStocksArbitrage(XXX):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        self.tickers = [
            'FCPP',  # Futebol Clube Do Porto
            'SPSO',  # Sporting Clube De Portugal
            'SLBEN', # Benfica
            'LAZI',  # Lazio
            'ASR',   # AS Rome
            'AJAX',  # AJAX
            'JUVE',  # Juventus
            'MANU',  # Manchester United
            'BVB',   # Dortmund
            'CCP',   # Celtic
            # 'BOLA' # Bali Bintang Sejahtera Tbk PT
        ]
        
        self.match_dates = {}
        
        for ticker in self.tickers:
            security = self.AddData(QuantpediaSoccer, ticker, Resolution.Daily)
            security.SetFeeModel(CustomFeeModel())
            security.SetLeverage(5)
            
        csv_string_file = self.Download('data.quantpedia.com/backtesting_data/equity/soccer/soccer_matches.csv')
        lines = csv_string_file.split('\r\n')
        for line in lines:
            line_split = line.split(';')
            date = datetime.strptime(line_split[0], "%d.%m.%Y").date()
            
            self.match_dates[date] = []
            for i in range(1, len(line_split)):
                ticker = line_split[i]
                self.match_dates[date].append(ticker)
        
    def OnData(self, data):
        self.Liquidate()
        
        short = []
        
        # Looking for todays date, because only daily closes are traded.
        today = (self.Time - timedelta(days=1)).date()
        
        if today in self.match_dates:
            for ticker in self.tickers:
                if ticker in self.match_dates[today] and ticker in data:
                    short.append(ticker)
                    
        for ticker in short:
            self.SetHoldings(ticker, -1 / len(short))

# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaSoccer(PythonData):
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource("data.quantpedia.com/backtesting_data/equity/soccer/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)

    def Reader(self, config, line, date, isLiveMode):
        data = QuantpediaSoccer()
        data.Symbol = config.Symbol
        
        if not line[0].isdigit(): return None
        split = line.split(';')
        
        data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
        data['price'] = float(split[1])
        data.Value = float(split[1])

        return data

# Custom fee model.
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading