Quando compramos fundos ou ações, costumamos usar a estratégia mais simples para tomar decisões: comprar na baixa e vender na alta.
Então, como você expressa essa estratégia em código? A primeira definição do sinal de negociação é: compre a 0,5%, a linha de lucro alvo é de 1,5% e a posição será liberada quando o rendimento alvo for atingido. Em seguida, vamos usar o Python para testar os ganhos dessa estratégia:
obter conjunto de dados
# 安装akshare
!pip3 install akshare --upgrade
# 调用包
import akshare as ak
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 不显示警告
import warnings
warnings.filterwarnings('ignore')
# 导入上证指数etf
data = ak.fund_etf_fund_info_em('510210','20210122','20230620')
# 筛选要用的时间段
data['year']=pd.to_datetime(data['净值日期']).dt.year # 增加年字段
df = data.loc[data['year']==2023,['净值日期','单位净值']] # 筛选数据集
df.index=range(len(df)) # 重排索引
df.tail(10) # 查看最近的n条数据
Código detalhado:
!pip3 install akshare Instale o pacote akshare para obter conjuntos de dados gratuitos.
fund_etf_fund_info_em() é a função do akshare para obter ETF, veja aqui para mais funções ↓ Aquisição de dados quantitativos da transação do akshare set_Como usar o blog do akshare_Zi Angzhang-CSDN Blog
pd.to_datetime() pode converter dados para o formato de hora. Originalmente, data['net value date'] está no formato de string; depois de converter para o formato de hora, dt.year pode ser chamado para retornar o ano.
O indexador .loc pode localizar linhas e filtrar colunas; data['year']==2023 retorna dados booleanos, o que significa localizar a linha especificada; ['net value date','unit net value'] significa filtrar o especificado coluna.
.index indica o índice do DateFrame de chamada, a função range() gera uma sequência de inteiros começando em 0 e len() obtém o comprimento ou número de linhas. Como os dados em 2023 são interceptados, o índice não começa em 0, portanto, ele precisa ser reatribuído ao índice.
resultado da operação:
Julgando os sinais de negociação
# 计算环比上一个交易日的涨跌幅
df['涨跌幅'] = df['单位净值'].pct_change()
# 判断买入信号和 对应交易数量
df.loc[df['涨跌幅']<-0.005,'买入数量'] = 1000
# 计算当天交易金额
df['交易金额'] = df['买入数量']*df['单位净值']
df = df.fillna(0.0) # 填补空值
df.tail(30)
Código detalhado:
pct_change() calcula a razão da cadeia e retorna o rendimento do dia, a fórmula é: razão da cadeia=período atual/período base-1.
df.loc[df['taxa de variação']<-0,005,'quantidade comprada'] usa o indexador .loc, df['taxa de variação']<-0,005 refere-se a uma diminuição de 0,5% em relação ao dia anterior, que é Sinal de negociação; se você comprar 1.000 ações para atender ao sinal de negociação, a 'quantidade de compra' entre colchetes é nosso parâmetro predefinido. Se houver um valor, o valor do resultado 1000 será armazenado no parâmetro padrão.
Depois de ter o número de transações por dia, multiplique pelo preço por ação do dia para obter o valor da transação do dia.
df.fillna(0.0) indica que o valor nulo recebe um valor de 0 para evitar que valores nulos subsequentes afetem o cálculo.
resultado da operação:
backtest
Na etapa de cálculo do sinal de negociação na etapa anterior, a quantidade da transação e o valor da transação comprada no dia são conhecidos, e a retenção histórica e a situação histórica de lucros e perdas devem ser calculadas a seguir.
# 定义全局变量
target_return = 0.015 # 止盈比例,收益率超过此比例时抛出
# 初始化变量
df['总持有数量'] = 0
total_num = 0 # 总持有数量
df['总持有成本'] = 0
total_cost = 0 # 总持有成本
df['持仓成本单价'] = 0
per_cost = 0 # 持仓单价
df['总市值'] = 0
stock = 0
df['盈亏额'] = 0
profit = 0
df['盈亏率'] = 0
profit_rate = 0
df['止盈线利润'] = 0
for i in range(len(df)):
total_num += df['买入数量'][i] # 累计数量
total_cost += df['交易金额'][i] # 累计成本
per_cost = total_cost/total_num
stock = total_num*df['单位净值'][i]
profit = stock - total_cost
profit_rate = df['单位净值'][i]/per_cost-1
df = df.fillna(0.0) # 填补空值
if profit_rate >= target_return:
total_num = 0
total_cost = 0
df['止盈线利润'][i] = profit
df['总持有数量'][i] = total_num
df['总持有成本'][i] = total_cost
df['持仓成本单价'][i] = per_cost
df['总市值'][i] = stock
df['盈亏额'][i] = profit
df['盈亏率'][i] = profit_rate
df.tail(30)
Código detalhado:
Como existe uma lógica de limpar 0 para atingir a taxa de retorno desejada, ela é representada por um loop for:
para i in range(len(df)) refere-se a percorrer cada linha de dados;
se profit_rate >= target_return significa que, quando for maior que o valor predefinido de 0,015, a quantidade de estoque total_num e o custo de estoque total_cost serão todos redefinidos para zero.
O significado de cada fórmula no loop for:
total_num += df['quantidade comprada'][i]: soma o número de ações compradas a cada dia para calcular a quantidade acumulada atual; total_cost
+= df['quantidade da transação'][i]: soma cada dia Calcula o acumulado custo de manutenção;
per_cost = total_cost/total_num: calcula o preço unitário de custo por ação;
estoque = total_num*df['valor líquido unitário'][i]: calcula o valor de mercado total atual;
lucro = estoque - custo_total: lucro total = total valor de mercado - custo total;
taxa_lucro = df['valor líquido unitário'][i]/custo-1: taxa de retorno=preço atual da ação/custo unitário por ação-1;
Uma vez que os resultados do cálculo de cada etapa devem ser armazenados em uma tabela bidimensional para posterior visualização da saída, cada campo a ser salvo precisa ser predefinido e atribuído um valor de 0. E atribua-o a uma variável no final de cada loop.
resultado da operação:
resultado de saída
max_cost = df['总持有成本'].max()
total_profit = df['止盈线利润'].sum()
profit_rate = total_profit/max_cost
sale_times = (df['止盈线利润']>0).sum()
print('最大持有成本:%f,总利润:%f,收益率:%f,达标次数:%s'%(max_cost,total_profit,profit_rate,sale_times))
Código detalhado:
A função max() retorna o valor máximo da coluna 'total holding cost', representando o custo máximo de entrada.
A função sum() retorna a soma da coluna 'Take Profit Line Profit', representando o total de lucros e perdas.
(df['Take Profit Line Profit']>0) é um julgamento condicional e retorna um valor booleano; como True significa 1, pode ser contado com sum().
resultado da operação:
Interpretação dos resultados:
De acordo com o mercado no primeiro semestre de 2023, o custo máximo de manutenção é inferior a 8k e a taxa de retorno com base no custo máximo é de 3,6%; nossa linha de lucro predefinida é de 1,5% e o número de liquidações quando a taxa de retorno alvo é atingida é de 6 vezes.
Usando os dados de 2021 e 2022 para backtesting, a taxa de retorno é próxima à taxa de juros dos depósitos, mas pode haver taxas de transação na oferta real que não foram levadas em consideração. Em geral, embora essa estratégia não esteja perdendo dinheiro , o retorno não é o ideal e precisamos continuar a explorar outras opções de método.