Python实现股票回测框架搭建,回测交易策略可行性。

文章目录

  • 什么是回测框架?
  • 回测框架
  • 交易历史数据
  • 回测报告
  • 回测示例
      • 关于Python技术储备
        • 一、Python所有方向的学习路线
        • 二、Python基础学习视频
        • 三、精品Python学习书籍
        • 四、Python工具包+项目源码合集
        • ①Python工具包
        • ②Python实战案例
        • ③Python小游戏源码
        • 五、面试资料
        • 六、Python兼职渠道


在这里插入图片描述

什么是回测框架?

无论是传统股票交易还是量化交易,无法避免的一个问题是我们需要检验自己的交易策略是否可行,而最简单的方式就是利用历史数据检验交易策略,而回测框架就是提供这样的一个平台让交易策略在历史数据中不断交易,最终生成最终结果,通过查看结果的策略收益,年化收益,最大回测等用以评估交易策略的可行性。

代码地址在最后。

本项目并不是一个已完善的项目, 还在不断的完善。

回测框架

回测框架应该至少包含两个部分, 回测类, 交易类.

回测类提供各种钩子函数,用于放置自己的交易逻辑,交易类用于模拟市场的交易平台,这个类提供买入,卖出的方法。

代码架构

以自己的回测框架为例。主要包含下面两个文件

backtest/
backtest.py
broker.py

backtest.py主要提供BackTest这个类用于提供回测框架,暴露以下钩子函数.

def initialize(self):"""在回测开始前的初始化"""passdef before\_on\_tick(self, tick):passdef after\_on\_tick(self, tick):passdef before\_trade(self, order):"""在交易之前会调用此函数可以在此放置资金管理及风险管理的代码如果返回True就允许交易,否则放弃交易"""return Truedef on\_order\_ok(self, order):"""当订单执行成功后调用"""passdef on\_order\_timeout(self, order):"""当订单超时后调用"""passdef finish(self):"""在回测结束后调用"""pass@abstractmethoddef on\_tick(self, bar):"""回测实例必须实现的方法,并编写自己的交易逻辑"""pass

玩过量化平台的回测框架或者开源框架应该对这些钩子函数不陌生,只是名字不一样而已,大多数功能是一致的,除了on_tick.

之所以是on_tick而不是on_bar, 是因为我希望交易逻辑是一个一个时间点的参与交易,在这个时间点我可以获取所有当前时间的所有股票以及之前的股票数据,用于判断是否交易,而不是一个时间点的一个一个股票参与交易逻辑。

而broker.py主要提供buy,sell两个方法用于交易。

def buy(self, code, price, shares, ttl=-1):"""限价提交买入订单---------Parameters:code:str股票代码price:float or None最高可买入的价格, 如果为None则按市价买入shares:int买入股票数量ttl:int订单允许存在的最大时间,默认为-1,永不超时---------return:dict{"type": 订单类型, "buy","code": 股票代码,"date": 提交日期,"ttl": 存活时间, 当ttl等于0时则超时,往后不会在执行"shares": 目标股份数量,"price": 目标价格,"deal\_lst": 交易成功的历史数据,如\[{"price": 成交价格,"date": 成交时间,"commission": 交易手续费,"shares": 成交份额}\]""}"""if price is None:stock\_info = self.ctx.tick\_data\[code\]price = stock\_info\[self.deal\_price\]order = {"type": "buy","code": code,"date": self.ctx.now,"ttl": ttl,"shares": shares,"price": price,"deal\_lst": \[\]}self.submit(order)return orderdef sell(self, code, price, shares, ttl=-1):"""限价提交卖出订单---------Parameters:code:str股票代码price:float or None最低可卖出的价格, 如果为None则按市价卖出shares:int卖出股票数量ttl:int订单允许存在的最大时间,默认为-1,永不超时---------return:dict{"type": 订单类型, "sell","code": 股票代码,"date": 提交日期,"ttl": 存活时间, 当ttl等于0时则超时,往后不会在执行"shares": 目标股份数量,"price": 目标价格,"deal\_lst": 交易成功的历史数据,如\[{"open\_price": 开仓价格,"close\_price": 成交价格,"close\_date": 成交时间,"open\_date": 持仓时间,"commission": 交易手续费,"shares": 成交份额,"profit": 交易收益}\]""}"""if code not in self.position:returnif price is None:stock\_info = self.ctx.tick\_data\[code\]price = stock\_info\[self.deal\_price\]order = {"type": "sell","code": code,"date": self.ctx.now,"ttl": ttl,"shares": shares,"price": price,"deal\_lst": \[\]}self.submit(order)return order

由于我很讨厌抽象出太多类,抽象出太多类及方法,我怕我自己都忘记了,所以对于对象的选择都是尽可能的使用常用的数据结构,如list, dict.

这里用一个dict代表一个订单。

上面的这些方法保证了一个回测框架的基本交易逻辑,而回测的运行还需要一个调度器不断的驱动这些方法,这里的调度器如下。

class Scheduler(object):"""
整个回测过程中的调度中心, 通过一个个时间刻度(tick)来驱动回测逻辑所有被调度的对象都会绑定一个叫做ctx的Context对象,由于共享整个回测过程中的所有关键数据,  
可用变量包括:  
    ctx.feed: {code1: pd.DataFrame, code2: pd.DataFrame}对象  ctx.now: 循环所处时间  ctx.tick\_data: 循环所处时间的所有有报价的股票报价  ctx.trade\_cal: 交易日历  ctx.broker: Broker对象  ctx.bt/ctx.backtest: Backtest对象
可用方法:    
 ctx.get\_hist"""def \_\_init\_\_(self):""""""self.ctx = Context()self.\_pre\_hook\_lst = \[\]self.\_post\_hook\_lst = \[\]self.\_runner\_lst = \[\]def run(self):# runner指存在可调用的initialize, finish, run(tick)的对象runner\_lst = list(chain(self.\_pre\_hook\_lst, self.\_runner\_lst, self.\_post\_hook\_lst))# 循环开始前为broker, backtest, hook等实例绑定ctx对象及调用其initialize方法for runner in runner\_lst:runner.ctx = self.ctxrunner.initialize()# 创建交易日历if "trade\_cal" not in self.ctx:df = list(self.ctx.feed.values())\[0\]self.ctx\["trade\_cal"\] = df.index# 通过遍历交易日历的时间依次调用runner# 首先调用所有pre-hook的run方法# 然后调用broker,backtest的run方法# 最后调用post-hook的run方法for tick in self.ctx.trade\_cal:self.ctx.set\_currnet\_time(tick)for runner in runner\_lst:runner.run(tick)# 循环结束后调用所有runner对象的finish方法for runner in runner\_lst:runner.finish()

在Backtest类实例化的时候就会自动创建一个调度器对象,然后通过Backtest实例的start方法就能启动调度器,而调度器会根据历史数据的一个一个时间戳不断驱动Backtest, Broker实例被调用。

为了处理不同实例之间的数据访问隔离,所以通过一个将一个Context对象绑定到Backtest, Broker实例上,通过self.ctx访问共享的数据,共享的数据主要包括feed对象,即历史数据,一个数据结构如下的字典对象。

{code1: pd.DataFrame, code2: pd.DataFrame}

而这个Context对象也绑定了Broker, Backtest的实例, 这就可以使得数据访问接口统一,但是可能导致数据访问混乱,这就要看策略者的使用了,这样的一个好处就是减少了一堆代理方法,通过添加方法去访问其他的对象的方法,真不嫌麻烦,那些人。

绑定及Context对象代码如下:

class Context(UserDict):def \_\_getattr\_\_(self, key):# 让调用这可以通过索引或者属性引用皆可return self\[key\]def set\_currnet\_time(self, tick):self\["now"\] = ticktick\_data = {}# 获取当前所有有报价的股票报价for code, hist in self\["feed"\].items():df = hist\[hist.index == tick\]if len(df) == 1:tick\_data\[code\] = df.iloc\[-1\]self\["tick\_data"\] = tick\_datadef get\_hist(self, code=None):"""如果不指定code, 获取截至到当前时间的所有股票的历史数据"""if code is None:hist = {}for code, hist in self\["feed"\].items():hist\[code\] = hist\[hist.index <= self.now\]elif code in self.feed:return {code: self.feed\[code\]}return hist
class Scheduler(object):"""
整个回测过程中的调度中心, 通过一个个时间刻度(tick)来驱动回测逻辑所有被调度的对象都会绑定一个叫做ctx的Context对象,由于共享整个回测过程中的所有关键数据,  可用变量包括:  
  ctx.feed: {code1: pd.DataFrame, code2: pd.DataFrame}对象ctx.now: 循环所处时间ctx.tick\_data: 循环所处时间的所有有报价的股票报价ctx.trade\_cal: 交易日历ctx.broker: Broker对象ctx.bt/ctx.backtest: Backtest对象
可用方法:  
  ctx.get\_hist"""def \_\_init\_\_(self):""""""self.ctx = Context()self.\_pre\_hook\_lst = \[\]self.\_post\_hook\_lst = \[\]self.\_runner\_lst = \[\]def add\_feed(self, feed):self.ctx\["feed"\] = feeddef add\_hook(self, hook, typ="post"):if typ == "post" and hook not in self.\_post\_hook\_lst:self.\_post\_hook\_lst.append(hook)elif typ == "pre" and hook not in self.\_pre\_hook\_lst:self.\_pre\_hook\_lst.append(hook)def add\_broker(self, broker):self.ctx\["broker"\] = brokerdef add\_backtest(self, backtest):self.ctx\["backtest"\] = backtest# 简写self.ctx\["bt"\] = backtestdef add\_runner(self, runner):if runner in self.\_runner\_lst:returnself.\_runner\_lst.append(runner)

为了使得整个框架可扩展,回测框架中框架中抽象了一个Hook类,这个类可以在在每次回测框架调用前或者调用后被调用,这样就可以加入一些处理逻辑,比如统计资产变化等。

这里创建了一个Stat的Hook对象,用于统计资产变化。

class Stat(Base):def \_\_init\_\_(self):self.\_date\_hist = \[\]self.\_cash\_hist = \[\]self.\_stk\_val\_hist = \[\]self.\_ast\_val\_hist = \[\]self.\_returns\_hist = \[\]def run(self, tick):self.\_date\_hist.append(tick)self.\_cash\_hist.append(self.ctx.broker.cash)self.\_stk\_val\_hist.append(self.ctx.broker.stock\_value)self.\_ast\_val\_hist.append(self.ctx.broker.assets\_value)@propertydef data(self):df = pd.DataFrame({"cash": self.\_cash\_hist,"stock\_value": self.\_stk\_val\_hist,"assets\_value": self.\_ast\_val\_hist}, index=self.\_date\_hist)df.index.name = "date"return df

而通过这些统计的数据就可以计算最大回撤年化率等。

def get\_dropdown(self):high\_val = -1low\_val = Nonehigh\_index = 0low\_index = 0dropdown\_lst = \[\]dropdown\_index\_lst = \[\]for idx, val in enumerate(self.\_ast\_val\_hist):if val >= high\_val:if high\_val == low\_val or high\_index >= low\_index:high\_val = low\_val = valhigh\_index = low\_index = idxcontinuedropdown = (high\_val - low\_val) / high\_valdropdown\_lst.append(dropdown)dropdown\_index\_lst.append((high\_index, low\_index))high\_val = low\_val = valhigh\_index = low\_index = idxif low\_val is None:low\_val = vallow\_index = idxif val < low\_val:low\_val = vallow\_index = idxif low\_index > high\_index:dropdown = (high\_val - low\_val) / high\_valdropdown\_lst.append(dropdown)dropdown\_index\_lst.append((high\_index, low\_index))return dropdown\_lst, dropdown\_index\_lst@propertydef max\_dropdown(self):"""最大回车率"""dropdown\_lst, dropdown\_index\_lst = self.get\_dropdown()if len(dropdown\_lst) > 0:return max(dropdown\_lst)else:return 0@propertydef annual\_return(self):"""年化收益率y = (v/c)^(D/T) - 1v: 最终价值c: 初始价值D: 有效投资时间(365)
    注: 虽然投资股票只有250天,但是持有股票后的非交易日也没办法投资到其他地方,所以这里我取365参考: [https://wiki.mbalib.com/zh-tw/%E5%B9%B4%E5%8C%96%E6%94%B6%E7%9B%8A%E7%8E%87](https://wiki.mbalib.com/zh-tw/%E5%B9%B4%E5%8C%96%E6%94%B6%E7%9B%8A%E7%8E%87)  
    """D = 365c = self.\_ast\_val\_hist\[0\]v = self.\_ast\_val\_hist\[-1\]days = (self.\_date\_hist\[-1\] - self.\_date\_hist\[0\]).daysret = (v / c) \*\* (D / days) - 1return ret

至此一个笔者需要的回测框架形成了。

交易历史数据

在回测框架中我并没有集成各种获取数据的方法,因为这并不是回测框架必须集成的部分,规定数据结构就可以了,数据的获取通过查看数据篇,

回测报告

回测报告我也放在了回测框架之外,这里写了一个Plottter的对象用于绘制一些回测指标等。结果如下:

回测示例

下面是一个回测示例。

import json
from backtest import BackTest
from reporter import Plotter
class MyBackTest(BackTest):def initialize(self):self.info("initialize")def finish(self):self.info("finish")def on\_tick(self, tick):tick\_data = self.ctx\["tick\_data"\]for code, hist in tick\_data.items():if hist\["ma10"\] > 1.05 \* hist\["ma20"\]:self.ctx.broker.buy(code, hist.close, 500, ttl=5)if hist\["ma10"\] < hist\["ma20"\] and code in self.ctx.broker.position:self.ctx.broker.sell(code, hist.close, 200, ttl=1)
if \_\_name\_\_ == '\_\_main\_\_':from utils import load\_histfeed = {}for code, hist in load\_hist("000002.SZ"):# hist = hist.iloc\[:100\]hist\["ma10"\] = hist.close.rolling(10).mean()hist\["ma20"\] = hist.close.rolling(20).mean()feed\[code\] = histmytest = MyBackTest(feed)mytest.start()order\_lst = mytest.ctx.broker.order\_hist\_lstwith open("report/order\_hist.json", "w") as wf:json.dump(order\_lst, wf, indent=4, default=str)stats = mytest.statstats.data.to\_csv("report/stat.csv")print("策略收益: {:.3f}%".format(stats.total\_returns \* 100))print("最大回彻率: {:.3f}% ".format(stats.max\_dropdown \* 100))print("年化收益: {:.3f}% ".format(stats.annual\_return \* 100))print("夏普比率: {:.3f} ".format(stats.sharpe))plotter = Plotter(feed, stats, order\_lst)plotter.report("report/report.png")

项目地址

https://github.com/youerning/stock_playground


关于Python技术储备

学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后给大家分享一份全套的 Python 学习资料,希望提供给那些想学习 Python 的小伙伴们一点帮助!

保存图片微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
在这里插入图片描述

二、Python基础学习视频

② 路线对应学习视频

还有很多适合0基础入门的学习视频,有了这些视频,轻轻松松上手Python~在这里插入图片描述
在这里插入图片描述

③练习题

每节视频课后,都有对应的练习题哦,可以检验学习成果哈哈!
在这里插入图片描述
因篇幅有限,仅展示部分资料

三、精品Python学习书籍

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。
在这里插入图片描述

四、Python工具包+项目源码合集
①Python工具包

学习Python常用的开发软件都在这里了!每个都有详细的安装教程,保证你可以安装成功哦!
在这里插入图片描述

②Python实战案例

光学理论是没用的,要学会跟着一起敲代码,动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。100+实战案例源码等你来拿!
在这里插入图片描述

③Python小游戏源码

如果觉得上面的实战案例有点枯燥,可以试试自己用Python编写小游戏,让你的学习过程中增添一点趣味!
在这里插入图片描述

五、面试资料

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
在这里插入图片描述
在这里插入图片描述

六、Python兼职渠道

而且学会Python以后,还可以在各大兼职平台接单赚钱,各种兼职渠道+兼职注意事项+如何和客户沟通,我都整理成文档了。
在这里插入图片描述
在这里插入图片描述
这份完整版的Python全套学习资料已经上传CSDN,朋友们如果需要可以保存图片微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/198669.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Footprint Analytics x Future3 万字研报:AI 与 Web3 数据行业融合的现状、竞争格局与未来机遇探析(上)

GPT的横空出世将全球的目光吸引至大语言模型&#xff0c;各行各业都尝试着利用这个“黑科技”提高工作效率&#xff0c;加速行业发展。Future3 Campus携手Footprint Analytics共同深入研究AI与Web3结合的无限可能&#xff0c;联合发布了《AI与Web3数据行业融合现状、竞争格局与…

写给初学者的 HarmonyOS 教程 -- 页面路由(router)

页面路由&#xff08;router&#xff09;是指在应用程序中实现不同页面之间的跳转和数据传递。 HarmonyOS 提供了 Router 模块&#xff0c;通过不同的 url 地址&#xff0c;可以方便地进行页面路由&#xff0c;轻松地访问不同的页面。 类似这样的效果&#xff1a; 页面跳转是…

香港服务器时间不准,差8小时

解决方案1 1、timedatectl查看系统时间 2、查看系统时区 ls /usr/share/zoneinfo 3、删除当前系统所处时区 rm /etc/localtime 4、创建软链接&#xff0c;以替换当前的时区信息 ln -s /usr/share/zoneinfo/Universal /etc/localtime 解决方案2 手动设置硬件时钟 1、设置系…

Hadoop学习笔记(HDP)-Part.15 安装HIVE

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

今日实施|解读新国标对数据库审计的能力要求

数据库审计是数据安全建设不可或缺的技术工具之一&#xff0c;无论是国家级的法律或标准&#xff0c;还是等保以及行业级的安全标准均对使用数据库审计有明确要求。据相关数据统计显示&#xff0c;数据库审计产品的市场需求已占据中国数据库安全市场容量的6成以上。 12月1日&am…

黑豹程序员-java发邮件,发送内容支持html,带多附件的案例

介绍 发邮件mail是常见的软件功能&#xff0c;下面利于spring和java的mail库实现发送内容支持html&#xff0c;带多附件的案例 开启SMTP邮件发送协议 谁提供的SMTP邮件服务&#xff0c;就找谁开启。QQ邮箱类似。 依赖 <!--Java MAil 发送邮件API--><dependency&g…

Linux 进程优先级

什么是进程的优先级 优先级&#xff1a;对资源的访问顺序&#xff01;注意优先级与权限的区别&#xff0c;优先级决定的是访问资源的顺序&#xff0c;这意味着无论是谁都可以访问到资源&#xff1b;但是如果你没有权限&#xff0c;你是不能访问资源的&#xff01; 这个应该比较…

el-date-picker时间控制范围为过去时间不可选

<el-date-picker :picker-options"startPickerOptions()" value-format"yyyy-MM-dd HH:mm:ss" v-model"form.applyFixPlan" type"datetime" placeholder"选择日期时间"> </el-date-picker> 在method中定义star…

c++ 三目运算符在类中的使用

简介 在类比较方面&#xff0c;三目运算符可以用于重载比较运算符。 代码示例1 #include <iostream> #include <cstring>class Person { public:Person(const char* name, int age) : m_age(age) {m_name new char[strlen(name) 1];strcpy(m_name, name);}~Pe…

HNU-电路与电子学-未知年份(不含解析)

【写在前面】 电路与电子学好像是从2020级开设的课程&#xff0c;故实际上目前只有2020与2021两个年级考过期末考试。 这门课程主要由所谓的“数电”与“模电”组成。而且先学的“模电”后学的“”数电&#xff0c;故期中考试主要以“模电”为主&#xff0c;期末考试主要以“…

前端vue导出PPT幻灯片,使用pptxgen.js,超详细(赋原数据)

即上一篇文章最终代码 前端vue导出PPT&#xff0c;使用pptxgen.js 前端vue导出PPT&#xff0c;使用pptxgen.js 一个平台下有10个国家&#xff0c;这个是后端返回数据固定的&#xff0c;每一个国家下面有10个物流方式&#xff0c;这10个物流方式是这10个国家都有的&#xff0c;…

redis配置介绍

redis配置详解 一、redis.conf二、持久化1、RDB① 触发机制② 优缺点③ 恢复rdb 2、AOF① 优缺点② 恢复aof 三、发布订阅 一、redis.conf # -----NETWORK----- # 设置绑定ip bind 127.0.0.1 -::1 # 设置redis保护&#xff0c;只能通过绑定在本地回环地址上的网络接口进行访问…

javaweb校车校园车辆管理系统springboot+jsp

结构设计&#xff1a;总体采用B/S结构设计模式 (1)用户登录模块&#xff1a;用户通过手动登录&#xff0c;检测是否是校内人员的车辆。 (2)用户车辆信息编辑、上传、模块&#xff1a;通过上传车辆入场信息的操作权限&#xff0c;以用户的名义发布资料上传至校园停车场系统中。…

在eclipse中安装python插件:PyDev

在eclipse中安装插件PyDev&#xff0c;就可以在eclipse中开发python了。 PyDev的官网&#xff1a;https://www.pydev.org/ 不过可以直接在eclipse中用Marketplace安装&#xff08;备注&#xff1a;有可能一次安装不成功&#xff0c;是因为下载太慢了&#xff0c;多试几次&…

3、RocketMQ源码分析(三)

RocketMQ源码-NameServer架构设计及启动流程 本文我们来分析NameServer相关代码&#xff0c;在正式分析源码前&#xff0c;我们先来回忆下NameServer的功能&#xff1a; NameServer是一个非常简单的Topic路由注册中心&#xff0c;其角色类似Dubbo中的zookeeper&#xff0c;支…

【AXI死锁】

单主机单从机死锁 AXI4没有WID,所以比较严格,即写数据通道的数据必须严格的按照写地址通道的数据顺序传送,比如AW通道发送ADDR0,ADDR1,ADDR2三笔写操作,每个写操作burst length=2,那么W通道的顺序在AXI4协议的规定下必须为:WDATA0_0,WDATA0_1,WDATA1_0,WDATA1_1,WDATA2_0…

LeetCode刷题---两两交换链表中的节点

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏&#xff1a;http://t.csdnimg.cn/D9LVS 前言&#xff1a;这个专栏主要讲述递归递归、搜索与回溯算法&#xff0c;所以下面题目主要也是这些算法做的 我讲述题目会把讲解部分分为3个部分…

dubbo框架技术文档-《spring-boot整合dubbo框架搭建+配置文件》框架的本地基础搭建

阿丹&#xff1a; 目前流行的微服务更多的就是dubbo和springcould微服务。之前阿丹没有出过dubbo相关的文章&#xff0c;因为之前接触springcould的微服务概念比较多一点&#xff0c;但是相对于springcould来说&#xff0c;springcould服务之间的调用是大多是使用了nacos&#…

每日一题:LeetCode-75. 颜色分类

每日一题系列&#xff08;day 12&#xff09; 前言&#xff1a; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f50e…