商品期貨基本面分析交易方法

一、摘要

在期貨交易市場,無論是私募機構還是普通散戶,爭論技術分析和基本面分析誰好誰壞,從來就沒有停止過。技術分析者認爲價格已經包含了一切,相信未來價格還以趨勢方式演變,只關心圖表上價格行爲本身的變化,判斷它能賣多少錢。基本面分析者認爲真正價值最終將會反映在價格上,並不需要關心短期價格走勢,更多的是分析影響價格背後的因素,判斷它值多少錢。

二、基本面分析的難點

之前聽過這樣一句話:“散戶可以完全無視基本面分析,只需要關注技術分析即可。”當時不以爲然,但隨着時間的推移,和大量實盤的磨礪,現在回過頭看,也漸漸接受了這種觀點,因爲傳統的基本面分析相對於散戶來說,門檻真的很高,想要分析某些事合力後會發生什麼結果,最起碼獲取與之關聯的數據是全面的、準確的。否則,再怎麼分析,也只是片面的。

影響期貨基本面分析的因素

  • 宏觀
    • 宏觀政策
    • 產業政策
    • 政治因素
    • 外匯匯率
    • 經濟週期
    • 貨幣政策
  • 品種
    • 升水貼水
    • 供需關係
    • 商品庫存
    • 產業利潤
  • 其他
    • 季節因素
    • 天氣因素
    • 新聞事件
    • 市場情緒

如上面這個列表,與商品期貨基本面分析有關的三大因素,林林總總多達數十項,往細了分,更有幾十項之多,並且這些數據是在不停變化的。單個散戶想要獲取這些龐大的數據已經是力所不及的事了,更不用提客觀分析。所以從這一點來講,在機構和散戶共存的市場中,散戶在起跑線上就已經處於劣勢中。儘管機構也不是近乎完美,但相對於散戶,有更多的優勢去獲取更多更準確的信息,以及素質更高的分析團隊。所以,在只論輸贏的交易市場,散戶的贏面很小。那麼有沒有合適散戶的基本面分析法呢?

三、期貨基本分析的核心

其實,期貨的基本面分析並不是想象中那麼難,只需要抓住基本面分析核心要素,就能剝絲抽繭從錯綜複雜的信息中找出規律。我們知道影響商品期貨的三大因素中包括:宏觀、品種、其他。宏觀經濟數據複雜多變,每天每時每刻,地球上有太多的經濟數據公佈,各國政界、央行、投行,官方的和非官方的。除了政治和經濟危機外,宏觀分析是聊天的好材料,實用性不大。美國著名的基金管理專家彼得·林奇曾發表看法:“我每年花在經濟大勢上的分析時間不超出十五分鐘”。

另外,在季節因素、天氣因素、新聞事件、市場情緒中,很多都是突發事件,本身就是無法分析預測的。所以散戶只需要把精力放在品種上即可,因爲期貨和現貨的價格都是公開的,可以計算出升水還是貼水。現貨的庫存數據在一些網站還是比較容易獲取的,可以預判出相對的供需關係等等,從而判斷期貨未來的大概價值。

升水貼水

同樣一個商品品種,在現貨市場與期貨市場的價格差,叫做基差。如果期貨價格大於現貨價格,我們稱之爲期貨升水;如果期貨價格小於現貨價格,我們稱之爲期貨貼水。無論是升水還是貼水,隨着交割日期的臨近,現貨價格與期貨價格都會趨於一致,一種是期貨向現貨迴歸,另一種是現貨向期貨迴歸。從期貨市場很難判斷基差究竟會以哪種方式迴歸,所以只能從現貨市場中尋找蛛絲馬跡。

供需關係
影響商品現貨價格的因素雖然有很多,但最終大都體現在供需關係上。如果買者多於賣者,價格就會上漲;如果賣者多於買者,價格就會下跌。國內的商品期貨大致上可以分爲:農產品和工業品。期貨圈子中流傳着這樣一句話:“農產品看供給,工業品看需求。”農產品是剛需,需求是相對穩定的,決定價格主要看供給;工業品是下游需求帶動的,再者國內基本都產能過剩,決定價格主要看需求。

雖然,在實際操作中我們很難獲取工業品的需求數據,也很難計算出農產品的供給數據。但是價格波動依存於供給與需求的相互作用,這種相互作用的結果就是庫存。如果庫存處於低位,說明市場供不應求,需求的力量大於供給的力量,未來價格看漲;如果庫存處於高位,說明市場供大於求,供給的力量大於需求的力量,未來價格看跌。

倉單
所謂的倉單就是交易所的交割倉庫入庫現貨後開具的標準倉單,它反映的是交易所公佈的庫存數量。當期貨價格較高時,現貨商就是註冊倉單然後在市場上銷售,所以根據這個原理我們可以反推出在期貨中的交易方向。

  • 期貨多頭:如果倉單大量減少,說明期貨價格低於現貨價格,應該做多。
  • 期貨空頭:如果倉單大量增加,說明期貨價格高於現貨價格,應該做空。

另外,還可以利用倉單來判斷庫存。倉單既可以註冊也可以註銷,當期貨主力想要價格上漲時,會把持有的註冊倉單註銷掉,改變交易所公佈的庫存數量,來達到交割貨物不足的假象,進而影響期貨價格上漲的預期。當期貨主力想要價格下跌時,會註冊倉單,造成交割貨物增多的假象,使得被動影響期貨價格下跌。

到這裏,基本面分析三大因素:庫存、基差、倉單就已經湊齊了,有些做基本面分析的朋友可能還會加上產業利潤、技術分析等等,增加窺視市場的維度,理論上兩者相加是大於二的,因爲能知道越多信息,越多的角度去觀察市場,才能做出更好的決策。那麼我們的基本面交易策略可以爲一下條件:

  • 多頭:貼水 + 低庫存 + 倉單減少
  • 空頭:升水 + 高庫存 + 倉單增加

四、獲取數據

接下來,我們就利用發明者量化交易平臺,來獲取這些數據吧!

'''backtest
start: 2019-01-01 00:00:00
end: 2019-08-09 00:00:00
period: 1d
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
'''


# 導入庫
import requests
import re
from bs4 import BeautifulSoup
import time
import datetime
import random
import json
import threading


# 請求頭文件
request_headers = {
    'Connection': 'keep-alive',
    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
    'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
    'accept-encoding': 'gzip, deflate',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36',
    'Referer': 'http://www.100ppi.com/sf2/day-2019-01-02.html'
}


# ip代理
proxies = {
    'http': 'http://182.35.82.128:9999', 'https': 'https://182.35.82.128:9999'
}


# 全局變量
diff_data = 0  # 存儲基差數據
reserve_data = 0  # 存儲庫存數據
receipt_data = 0  # 存儲倉單數據
profit_data = 0  # 存儲虛擬利潤數據


# 日期轉時間戳
def to_timestamp(date_str):
    times = date_str + " 00:00:00"
    time_array = time.strptime(times, "%Y-%m-%d %H:%M:%S")
    return int(round(time.mktime(time_array) * 1000))


# 返回日期數組
def date_arr(year, month, day):
    begin = datetime.date(year, month, day)
    end = datetime.date.today()
    arr = []
    for i in range((end - begin).days + 1):
        day = begin + datetime.timedelta(days=i)
        arr.append([str(day).replace('-', ''), str(day),
                    day.weekday() + 1, to_timestamp(str(day))])
    return arr


# 獲取基差
def spot_futures_diff_data(date, futures_name):
    global diff_data
    url = f"http://www.100ppi.com/sf2/day-{date}.html"  # 獲取生意社的基差數據
    try:
        url_text = requests.get(
            url, headers=request_headers).text
    except BaseException:
        Log('獲取基差數據失敗')
        return int(diff_data)
    soup = BeautifulSoup(url_text, "html5lib")
    if len(soup.select("#fdata")) > 0:
        results = soup.select("#fdata")[0]
        for i in results.find_all('tr'):
            if len(i.find_all('td', text=futures_name)) > 0:
                data = i.find_all('font')[0].text
                if data is not None:
                    diff_data = data
    return int(diff_data)


# 獲取庫存
def spot_reserve_data(date, futures_name):
    global reserve_data
    # 獲取上期所的庫存數據
    url = f'http://www.shfe.com.cn/data/dailydata/{date}weeklystock.dat'
    try:
        url_text = requests.get(url, headers=request_headers).text
    except BaseException:
        print('獲取庫存數據失敗')
        return reserve_data
    total = 0
    count = 0
    if url_text[0] == '{':
        for i in json.loads(url_text)['o_cursor']:
            if futures_name in i['VARNAME']:
                if '合計' not in i['WHABBRNAME'] and '總計' not in i['WHABBRNAME']:
                    try:
                        inventory = int(i['WHSTOCKS'])
                    except BaseException:
                        return reserve_data
                    if inventory > 0:
                        total = total + inventory
                        count = count + 1
        if count > 0:
            reserve_data = int(total / count)
    return reserve_data


# 獲取倉單
def spot_receipt_data(date, futures_name):
    global receipt_data
    # 獲取上期所的倉單數據
    url = f'http://www.shfe.com.cn/data/dailydata/{date}dailystock.dat'
    try:
        url_text = requests.get(url, headers=request_headers).text
    except BaseException:
        print('獲取庫存數據失敗')
        return receipt_data
    total = 0
    count = 0
    if url_text[0] == '{':
        for i in json.loads(url_text)['o_cursor']:
            if futures_name in i['VARNAME']:
                if '合計' not in i['WHABBRNAME'] and '總計' not in i['WHABBRNAME']:
                    try:
                        inventory = int(i['WRTWGHTS'])
                    except BaseException:
                        return receipt_data
                    if inventory > 0:
                        total = total + inventory
                        count = count + 1
        if count > 0:
            receipt_data = int(total / count)
    return receipt_data


# 虛擬利潤
def get_profit_data():
    global profit_data
    profit_data = profit_data + random.randint(-100, 150)
    return profit_data


# 程序入口
def main():
    # threading.Thread(target=spot_futures_diff_arr, args=('天然橡膠',)).start()
    # threading.Thread(target=commodity_inventory_arr, args=('天然橡膠',)).start()
    # threading.Thread(target=commodity_warehouse_receipt_arr, args=('天然橡膠',)).start()

    # 基差圖表
    cfgA = {
        "extension": {
            "layout": 'single',
            "col": 6,
            "height": "500px",
        },
        "title": {
            "text": "基差圖表"
        },
        "xAxis": {
            "type": "datetime"
        },
        "series": [{
            "name": "基差",
            "data": [],
        }]
    }

    # 庫存圖表
    cfgB = {
        "extension": {
            "layout": 'single',
            "col": 6,
            "height": "500px",
        },
        "title": {
            "text": "庫存圖表"
        },
        "xAxis": {
            "type": "datetime"
        },
        "series": [{
            "name": "庫存",
            "data": [],
        }]
    }

    # 倉單圖表
    cfgC = {
        "extension": {
            "layout": 'single',
            "col": 6,
            "height": "500px",
        },
        "title": {
            "text": "倉單圖表"
        },
        "xAxis": {
            "type": "datetime"
        },
        "series": [{
            "name": "倉單",
            "data": [],
        }]
    }

    # 虛擬利潤圖表
    cfgD = {
        "extension": {
            "layout": 'single',
            "col": 6,
            "height": "500px",
        },
        "title": {
            "text": "虛擬利潤圖表"
        },
        "xAxis": {
            "type": "datetime"
        },
        "series": [{
            "name": "利潤",
            "data": [],
        }]
    }
    LogReset()  # 清除日誌
    chart = Chart([cfgA, cfgB, cfgC, cfgD])  # 創建圖表
    chart.reset()  # 初始清空圖表
    for i in date_arr(2018, 1, 1):  # 從2018年1月1日開始獲取歷史數據
        diff = spot_futures_diff_data(i[1], '天然橡膠')  # 獲取基差數據
        reserve = spot_reserve_data(i[0], '天然橡膠')  # 獲取庫存數據
        receipt = spot_receipt_data(i[0], '天然橡膠')  # 獲取倉單數據
        profit = get_profit_data()  # 虛擬利潤
        if diff != 0 and reserve != 0 and receipt != 0:  # 如果所有的數據都不爲0
            chart.add(0, [i[3], diff])  # 繪製基差數據
            chart.add(1, [i[3], reserve])  # 繪製庫存數據
            chart.add(2, [i[3], receipt])  # 繪製倉單數據
            chart.add(3, [i[3], profit])  # 繪製虛擬利潤數據
            chart.update([cfgA, cfgB, cfgC, cfgD])  # 重置圖表配置
            time.sleep(1)  # 休眠1秒
            Log(f'基差:{diff} 庫存:{reserve} 倉單:{receipt} 日期:{i[1]}')  # 輸出信息到日誌

完整的代碼已經分享到發明者量化交易平臺 https://www.fmz.com/strategy/161412 可以直接複製策略無需設置,直接創建機器人即可。

五、實盤運行

六、總結

回到本篇的開場,基本面分析和技術分析並不存在孰優孰劣,它們探究的是同一個市場,只是站的角度不同。沒有人可以僅憑一個角度分析,就能窺視市場全部。我相信兩者相加是大於二的,因爲能知道越多信息,越多的角度去觀察市場,才能做出更好的決策。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章